FCM 注册令牌管理的最佳做法

如果您使用 FCM API 以编程方式构建发送请求,那么随着时间的推移可能会发现,使用过时的注册令牌向不活跃的设备发送消息是在浪费资源。这种情况可能会影响 Firebase 控制台中报告的消息传送数据或导出到 BigQuery 的数据,表现为传送速率明显(但并非真正有效)下降。本指南介绍了一些您可以采取的措施,以帮助确保实现高效的消息定位和有效的传送报告。

过时和过期的注册令牌

过时注册令牌是指与已超过一个月未连接到 FCM 的不活跃设备相关联的令牌。随着时间的推移,设备再次连接到 FCM 的可能性会越来越小。这些过时令牌的消息发送和主题扇出不太可能进行传送。

令牌过时的原因有很多。例如,令牌关联的设备可能会丢失、销毁或存储起来而被遗忘。

当过时令牌达到 270 天处于非活跃状态时,FCM 会将其视为过期令牌。令牌过期后,FCM 会将其标记为无效,并拒绝向其发送内容。不过,在设备再次连接并打开应用的极少数情况下,FCM 会为应用实例发出新的令牌。

基本最佳实践

在使用 FCM API 以编程方式构建发送请求的任何应用中,您都应该遵循一些基本做法。主要的最佳实践包括:

  • FCM 检索注册令牌,并将其存储在服务器上。服务器的一个重要作用是跟踪每个客户端的令牌,并保留一份不断更新的活跃令牌列表。我们强烈建议您在代码和服务器中实现令牌时间戳,并定期更新此时间戳。
  • 保持令牌时效性并移除过时令牌。除了移除 FCM 不再认为有效的令牌之外,您可能还需要监控其他表明令牌过时的信号,并主动将这类令牌移除。本指南将介绍一些旨在实现这一目标的方法。

检索和存储注册令牌

初次启动您的应用时,FCM SDK 会为客户端应用实例生成一个注册令牌。您必须将该令牌包含在从 API 发出的设置了定位目标的发送请求中,或添加到主题订阅中以便定位主题。

我们强烈建议您的应用应在初始启动时检索此令牌,并将其与时间戳一起保存到应用服务器。此时间戳必须通过您的代码和服务器实现,因为 FCM SDK 并不会向您提供它。

此外,请务必将令牌保存到服务器并在每次更改时更新时间戳,例如,在这些情况下:

  • 应用在新设备上恢复
  • 用户卸载或重新安装应用
  • 用户清除应用数据
  • FCM 使应用的现有令牌过期后,应用再次变为活跃状态

示例:在 Cloud Firestore 中存储令牌和时间戳

例如,您可以使用 Cloud Firestore 将令牌存储在名为 fcmTokens 的集合中。集合中的每个文档 ID 都对应一个用户 ID,并且文档会存储当前注册令牌及其上次更新时间戳。使用 set 函数,如以下 Kotlin 示例所示:

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM registration token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private fun sendTokenToServer(token: String?) {
        // If you're running your own server, call API to send token and today's date for the user

        // Example shown below with Firestore
        // Add token and timestamp to Firestore for this user
        val deviceToken = hashMapOf(
            "token" to token,
            "timestamp" to FieldValue.serverTimestamp(),
        )
        // Get user ID from Firebase Auth or your own server
        Firebase.firestore.collection("fcmTokens").document("myuserid")
            .set(deviceToken)
    }

每次检索令牌时,系统都会通过调用 sendTokenToServer 将其存储在 Cloud Firestore 中:

    /**
     * Called if the FCM registration token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the
     * FCM registration token is initially generated so this is where you would retrieve the token.
     */
    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // FCM registration token to your app server.
        sendTokenToServer(token)
    }
        var token = Firebase.messaging.token.await()

        // Check whether the retrieved token matches the one on your server for this user's device
        val preferences = this.getPreferences(Context.MODE_PRIVATE)
        val tokenStored = preferences.getString("deviceToken", "")
        lifecycleScope.launch {
            if (tokenStored == "" || tokenStored != token)
            {
                // If you have your own server, call API to send the above token and Date() for this user's device

                // Example shown below with Firestore
                // Add token and timestamp to Firestore for this user
                val deviceToken = hashMapOf(
                    "token" to token,
                    "timestamp" to FieldValue.serverTimestamp(),
                )

                // Get user ID from Firebase Auth or your own server
                Firebase.firestore.collection("fcmTokens").document("myuserid")
                    .set(deviceToken).await()
            }
        }

保持令牌时效性并移除过时令牌

有时您并不能够简单直接地确定令牌是最新的还是过时的。如需涵盖所有情况,应该采用阈值来确定何时将令牌视为过时。默认情况下,如果应用实例已一个月未连接,FCM 会将令牌视为过时。任何超过一个月的令牌都可能是不活跃的设备;否则活跃设备会更新其令牌。

根据您的应用场景,一个月的时间可能过短或过长,因此您可以自行确定适合自己的标准。

检测来自 FCM 后端的无效令牌响应

请务必检测来自 FCM 的无效令牌响应,并采取处理措施:从您的系统中删除任何已知无效或过期的注册令牌。对于 HTTP v1 API,这些错误消息可能表明您的发送请求是以无效或过期的令牌为目标的:

  • UNREGISTERED (HTTP 404)
  • INVALID_ARGUMENT (HTTP 400)

如果您确定消息载荷有效,并且针对目标令牌收到以上任一响应,便可以安全地删除此令牌的记录,因为它将永远无效。例如,如需从 Cloud Firestore 中删除无效令牌,您可以部署并运行如下函数:

    // Registration token comes from the client FCM SDKs
    const registrationToken = 'YOUR_REGISTRATION_TOKEN';

    const message = {
    data: {
        // Information you want to send inside of notification
    },
    token: registrationToken
    };

    // Send message to device with provided registration token
    getMessaging().send(message)
    .then((response) => {
        // Response is a message ID string.
    })
    .catch((error) => {
        // Delete token for user if error code is UNREGISTERED or INVALID_ARGUMENT.
        if (errorCode == "messaging/registration-token-not-registered") {
            // If you're running your own server, call API to delete the
            token for the user

            // Example shown below with Firestore
            // Get user ID from Firebase Auth or your own server
            Firebase.firestore.collection("fcmTokens").document(user.uid).delete()
        }
    });

只有在令牌在 270 天后过期或客户端明确取消注册的情况下,FCM 才会返回无效令牌响应。如果您需要根据自己的定义更准确地跟踪过时情况,可以主动移除过时注册令牌

定期更新令牌

我们建议您定期检索和更新服务器上的所有注册令牌。 这要求您执行以下操作:

  • 在客户端应用中添加应用逻辑,以使用适当的 API 调用(例如,对于 Apple 平台,token(completion):;对于 Android,getToken())检索当前令牌,然后将当前令牌发送至应用服务器进行存储(带有时间戳)。这可能是每月都要执行的作业,配置目的是涵盖所有客户端或令牌。
  • 添加服务器逻辑以定期更新令牌的时间戳,无论令牌是否已更改。

如需查看使用 WorkManager 更新令牌的 Android 逻辑示例,请参阅 Firebase 博客上的管理 Cloud Messaging 令牌

无论您遵循哪种计时模式,都请务必定期更新令牌。采用每月一次的更新频率应该能够在电池影响与检测不活跃注册令牌之间达到很好的平衡。通过执行这种更新,还可以确保任何进入不活跃状态的设备在重新进入活跃状态时更新其注册状态。将更新频率设置为短于每周一次不会带来任何好处。

移除过时注册令牌

在向设备发送消息之前,请确保设备的注册令牌时间戳在过时期限内。例如,您可以实现 Cloud Functions for Firebase 以运行每日检查,确保时间戳在定义的过时期限(例如 const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30;)内,然后移除过时令牌:

exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => {
  // Get all documents where the timestamp exceeds is not within the past month
  const staleTokensResult = await admin.firestore().collection('fcmTokens')
      .where("timestamp", "<", Date.now() - EXPIRATION_TIME)
      .get();
  // Delete devices with stale tokens
  staleTokensResult.forEach(function(doc) { doc.ref.delete(); });
});

退订主题的过时令牌

如果您使用主题,可能还需要从订阅的主题取消注册过时令牌。这涉及到两个步骤:

  1. 您的应用应在每月以及在注册令牌发生更改时重新订阅一次主题。这样可以形成一个自我修复的解决方案,每当应用重新进入活跃状态时,即可自动进行订阅。
  2. 如果应用实例空闲一个月(或您自己设置的过时期限),您应该使用 Firebase Admin SDK 将其退订主题,以便从 FCM 后端删除令牌到主题的映射。

这两个步骤的好处在于,扇出速度更快,因为要扇出的过时令牌较少,并且过时的应用实例会在重新进入活跃状态之后自动重新订阅。

衡量传送是否成功

为了获得最准确的消息传送情况,最好仅向活跃使用的应用实例发送消息。如果您定期向拥有大量订阅者的主题发送消息,这一点尤为重要;如果其中部分订阅者实际上处于不活跃状态,那么随着时间的推移,对传送统计信息产生的影响将非常显著。

在将消息定位到令牌之前,请考虑:

  • Google Analytics、在 BigQuery 中捕获的数据或其他跟踪信号是否表示令牌有效?
  • 之前的传送尝试是否在一段时间内总是失败?
  • 在过去一个月中,您的服务器上的注册令牌是否已更新?
  • 对于 Android 设备,FCM Data API 是否报告了较高的因 droppedDeviceInactive 而导致消息传送失败的百分比?

如需详细了解传送,请参阅了解消息传送