如果您使用 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(); });
});
取消訂閱主題的舊憑證
如果您使用主題,也建議你從訂閱的主題中取消註冊過時的憑證。這項程序涉及兩個步驟:
- 每當註冊權杖變更時,應用程式每個月應重新訂閱一次主題。這會形成自修復解決方案,當應用程式再次啟用時,訂閱項目就會自動重新顯示。
- 如果應用程式執行個體閒置一個月 (或您自訂的失效時間窗格),您應使用 Firebase Admin SDK 取消訂閱主題,以便從 FCM 後端刪除符記與主題的對應項目。
這兩個步驟的好處是,由於要分散的舊憑證數量較少,因此分散作業會更快完成,而舊的應用程式執行個體重新啟用後,也會自動重新訂閱。
評估提交成效
如要取得最準確的訊息傳送情況,請盡量只將訊息傳送至目前使用的應用程式執行個體。如果您會定期將訊息傳送至有大量訂閱者的主題,這一點尤其重要;如果其中有部分訂閱者實際上處於閒置狀態,那麼長期下來對您傳送統計資料的影響可能會很嚴重。
在將訊息指定給符記之前,請考量以下事項:
- Google Analytics、BigQuery 中擷取的資料或其他追蹤信號是否顯示代碼處於啟用狀態?
- 之前是否曾經歷一段時間內持續出現提交失敗的情況?
- 過去一個月內,您是否在伺服器上更新註冊權杖?
- 對於 Android 裝置,FCM Data API 是否回報由於
droppedDeviceInactive
導致訊息傳送失敗的比例偏高?
如要進一步瞭解傳送作業,請參閱「瞭解訊息傳送作業」。