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 呼叫(例如token(completion):對於 Apple 平台或getToken()對於Android 平台)檢索目前令牌,然後將目前令牌傳送到您的應用程式伺服器進行儲存(使用時間戳記)。這可能是配置為涵蓋所有客戶端或令牌的每月作業。
  • 新增伺服器邏輯以定期更新令牌的時間戳,無論令牌是否已變更。

有關使用WorkManager更新令牌的 Android 邏輯範例,請參閱 Firebase 部落格上的管理雲端訊息傳遞令牌

無論您遵循哪種計時模式,請確保定期更新令牌。每月一次的更新頻率在電池影響和檢測不活動註冊令牌之間取得了良好的平衡。透過執行此刷新,您還可以確保任何處於非活動狀態的裝置在再次變為活動狀態時都會刷新其註冊。比每週更頻繁地進行刷新沒有任何好處。

刪除過時的註冊令牌

在向設備發送訊息之前,請確保設備註冊令牌的時間戳在您的過時視窗期內。例如,您可以實作 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 資料 API是否會報告由於droppedDeviceInactive導致的訊息傳送失敗比例較高?

有關傳遞的更多信息,請參閱了解訊息傳遞