使用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
相关产品推荐
相关产品推荐

