You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS应用关闭(非后台)时接收推送自动递增角标方案咨询

嘿,这个问题我之前帮不少做聊天室类应用的开发者解决过——服务器逐个维护每个设备的角标计数确实太繁琐,还拉高了成本,给你几个更高效的方案,其中第一个完全匹配你的需求:

方案一:用Notification Service Extension实现自动增量角标(推荐)

这个方案的核心是利用iOS的通知扩展,在推送到达设备时(哪怕应用完全关闭)自动处理角标累加,服务器根本不需要维护每个设备的角标数,批量推送同一个payload就行。

实现步骤:

  • 创建Notification Service Extension:在Xcode里给项目添加一个「Notification Service Extension」,这个扩展会在推送到达设备时被短暂唤醒,和主应用是否运行无关。
  • 配置App Groups:因为主应用和扩展需要共享角标计数,所以要在两者的Capabilities里开启App Groups,设置同一个Group ID(比如group.com.yourteam.yourapp)。
  • 扩展里处理角标增量:在扩展的didReceive方法中,读取共享存储的当前角标数,加上增量(每条推送加1),再更新通知的badge字段,最后保存新的计数。
  • 主应用重置角标:当用户打开应用时,把角标清零,同时同步共享存储的计数。

代码示例:

Notification Service Extension代码:

import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        guard let content = bestAttemptContent else {
            contentHandler(request.content)
            return
        }
        
        // 从自定义字段获取增量,默认加1
        let increment = content.userInfo["badge_increment"] as? Int ?? 1
        
        // 从共享UserDefaults读取当前角标数
        let sharedDefaults = UserDefaults(suiteName: "group.com.yourteam.yourapp")!
        var currentBadge = sharedDefaults.integer(forKey: "current_badge")
        currentBadge += increment
        
        // 更新通知角标
        content.badge = NSNumber(value: currentBadge)
        
        // 保存更新后的计数
        sharedDefaults.set(currentBadge, forKey: "current_badge")
        sharedDefaults.synchronize()
        
        contentHandler(content)
    }
    
    override func serviceExtensionTimeWillExpire() {
        // 超时 fallback,返回原始通知内容
        if let handler = contentHandler, let content = bestAttemptContent {
            handler(content)
        }
    }
}

主应用重置角标代码(AppDelegate或SceneDelegate):

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 清零角标
        UIApplication.shared.applicationIconBadgeNumber = 0
        
        // 同步共享存储的计数为0
        let sharedDefaults = UserDefaults(suiteName: "group.com.yourteam.yourapp")!
        sharedDefaults.set(0, forKey: "current_badge")
        sharedDefaults.synchronize()
        
        return true
    }
}

服务器端推送Payload示例(批量推送所有设备用同一个):

{
    "aps": {
        "alert": {
            "title": "新聊天消息",
            "body": "有人@了你"
        },
        "mutable-content": 1, // 必须设置这个,才会触发通知扩展
        "sound": "default"
    },
    "badge_increment": 1 // 自定义字段,告诉扩展要加1
}

注意事项:

  • 确保推送的mutable-content字段设为1,否则扩展不会被触发。
  • 应用卸载重装后,共享存储的计数会丢失,第一次推送会从0开始累加;如果需要准确对应服务器未读数,可以在主应用启动时请求服务器同步一次。
  • 扩展的运行时间有限(约30秒),所以逻辑要尽量简单,避免复杂操作。

方案二:静默推送同步未读计数(适合需精准计数的场景)

如果你的角标需要严格对应服务器上的未读消息数(比如可能存在消息撤回、批量标记已读的情况),可以用这个方案:

  • 服务器维护每个用户的未读消息数(而不是每个设备)。
  • 有新消息时,给用户的所有设备发送静默推送content-available:1,无alert/sound),触发客户端(后台或刚关闭时,需用户开启后台刷新)请求服务器获取最新未读数,然后更新角标。
  • 缺点:应用完全关闭且用户没开后台刷新时,静默推送不会触发,角标不会实时更新。

方案三:利用APNS批量推送的简化处理(仅限iOS 15+)

iOS 15及以后,APNS支持在批量推送时,给不同设备传递不同的badge值,但这需要服务器知道每个设备的当前角标数,本质还是要维护设备级计数,只是APNS提供了批量替换的接口,比逐个循环稍高效,但还是不如方案一彻底。

内容的提问来源于stack exchange,提问作者alionthego

火山引擎 最新活动