You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Firebase主题通知时如何处理UNREGISTERED令牌?

Firebase主题推送的无效令牌处理与最佳实践

问题1:使用主题通知时,有没有办法处理UNREGISTERED令牌?

FCM主题推送采用批量异步处理机制,调用send()发送主题消息后,仅会返回全局消息ID,不会返回单个令牌的错误详情(包括UNREGISTERED状态)。因此直接通过主题推送无法获取具体的无效令牌。

替代方案是使用批量消息推送(Multicast Messages):通过sendMulticast()方法一次最多发送500个令牌,API会返回每个令牌的发送结果,你可以从中筛选出UNREGISTERED的令牌进行清理。这种方式既保留了错误追踪能力,效率也远高于逐个发送。

问题2:给10万+设备逐个发送通知是否是推荐做法?

绝对不推荐。逐个发送会产生大量API请求,不仅耗时久,还容易触发FCM的限流机制,同时极大浪费服务端资源和网络带宽。

正确做法是用批量推送接口:将10万+令牌分成每500个一组,循环调用sendMulticast(),既能保证推送效率,又能获取每个令牌的发送状态,方便后续清理无效数据。

问题3:使用主题通知时,管理令牌有效性的最佳实践?

  • 客户端主动同步令牌:客户端在令牌更新(如APP重装、设备系统更新)时,主动向服务端上报新令牌,同时标记旧令牌为失效。
  • 定期批量验证:每隔一段时间(比如每周),从数据库抽取部分令牌(或分组小批量)用sendMulticast()发送测试消息,清理返回UNREGISTERED的令牌。
  • 结合用户活跃数据:FCM自动移除卸载设备的主题订阅存在延迟,可结合服务端的用户最后活跃时间,清理长期未活跃的设备令牌。
  • 避免冗余订阅:确保每个设备令牌仅订阅一次目标主题,重复订阅不影响推送,但会增加无效数据维护成本。
  • 精准错误处理:批量推送时捕获FirebaseMessagingException,针对UNREGISTERED、INVALID_TOKEN等明确的无效状态,直接从数据库删除对应令牌。

通过主题向所有设备发送通知的代码

private fun sendToTopic(
    firebaseApp: FirebaseApp,
    title: String,
    body: String
): CustomPushNotificationResponse {
    val message = Message.builder()
        .setTopic(FCMUtils.TOPIC_LOGSNAIL)
        .setNotification(
            Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build()
        )
        .build()

    return try {
        val messageId = FirebaseMessaging.getInstance(firebaseApp).send(message)
        CustomPushNotificationResponse(isSuccess = true, messageId = messageId)
    } catch (e: FirebaseMessagingException) {
        CustomPushNotificationResponse(isSuccess = false, errorMessage = e.message)
    } catch (e: Exception) {
        CustomPushNotificationResponse(isSuccess = false, errorMessage = e.message)
    }
}

向特定设备发送通知的代码

private fun sendToDevice(
    firebaseApp: FirebaseApp,
    deviceUuid: String,
    title: String,
    body: String
): CustomPushNotificationResponse {
    val device = deviceRepository.findByDeviceUuid(deviceUuid)
        .orElseThrow { IllegalArgumentException("Device with UUID $deviceUuid not found") }

    val token = device.androidFirebaseToken
        ?: return CustomPushNotificationResponse(
            isSuccess = false,
            errorMessage = "Firebase token not available for device UUID $deviceUuid"
        )

    val message = Message.builder()
        .setToken(token)
        .setNotification(
            Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build()
        )
        .build()

    return try {
        val messageId = FirebaseMessaging.getInstance(firebaseApp).send(message)
        CustomPushNotificationResponse(isSuccess = true, messageId = messageId)
    } catch (e: FirebaseMessagingException) {
        handleUnregisteredToken(device, e)
    } catch (e: Exception) {
        CustomPushNotificationResponse(isSuccess = false, errorMessage = e.message)
    }
}

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:15:17