管理 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 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 導致訊息傳送失敗的比例偏高?

如要進一步瞭解傳送作業,請參閱「瞭解訊息傳送作業」。