如果您使用 FCM API 以程式設計方式建置發送請求,您可能會發現,隨著時間的推移,您會透過向具有陳舊註冊令牌的非活動裝置發送訊息來浪費資源。這種情況可能會影響 Firebase 控制台中報告的訊息傳送資料或匯出到 BigQuery 的數據,表現為傳送率急劇(但實際上不是有效)下降。本指南討論了您可以採取的一些措施,以幫助確保有效的訊息定位和有效的傳遞報告。
基本最佳實踐
在任何使用 FCM API 以程式設計方式建立發送請求的應用程式中,您都應該遵循一些基本實踐。主要的最佳實踐是:
- 將註冊令牌儲存在您的伺服器上。伺服器的一個重要作用是追蹤每個客戶端的令牌並保留更新的活動令牌清單。我們強烈建議在您的程式碼和伺服器中實作令牌時間戳,並定期更新此時間戳記。
- 刪除已過時的儲存令牌。除了在明顯的無效令牌回應情況下刪除令牌之外,您可能還需要監視令牌已過時的其他跡象。本指南討論了實現此目標的一些選項。
檢索並儲存註冊令牌
在應用程式首次啟動時,FCM SDK 會為客戶端應用程式實例產生註冊令牌。這是您必須包含在來自 API 的目標傳送請求中的令牌,或是新增到目標主題的主題訂閱中的令牌。
正如我們的客戶端設定指南中所述,您的應用程式應在初始啟動時檢索此令牌,並將其與時間戳記一起儲存到您的應用程式伺服器。此時間戳必須由您的程式碼和伺服器實現,因為 FCM SDK 不為您提供它。
此外,將令牌保存到伺服器並在時間戳發生變化時更新時間戳也很重要,例如:
- 該應用程式已在新設備上恢復
- 用戶卸載/重新安裝應用程式
- 用戶清除應用程式資料。
範例:在 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 的無效令牌回應,並透過從系統中刪除任何已知無效的註冊令牌來進行回應。使用 HTTP v1 API,這些錯誤訊息可能表示您的傳送請求針對陳舊或無效的令牌:
-
UNREGISTERED
(HTTP 404) -
INVALID_ARGUMENT
(HTTP 400)
請注意,由於在訊息有效負載出現問題時也會引發INVALID_ARGUMENT
,因此只有當有效負載完全有效時,它才會發出無效令牌訊號。有關詳細信息,請參閱錯誤代碼。
如果您確定訊息有效負載有效並且您收到目標令牌的這些回應中的任何一個,則可以安全地刪除此令牌的記錄,因為它永遠不會再有效。例如,要從 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()
}
});
確保註冊令牌的新鮮度
確定令牌是新鮮還是陳舊並不總是那麼簡單。為了涵蓋所有情況,您應該在認為代幣過時時採用一個閾值;我們的建議是兩個月。任何超過兩個月的令牌都可能是不活動的設備;否則,活動設備將刷新其令牌。
定期更新令牌
我們建議您定期檢索並更新伺服器上的所有註冊令牌。這需要您:
- 在客戶端應用程式中新增應用程式邏輯,以使用適當的 API 呼叫(例如
token(completion):
對於 Apple 平台或getToken()
對於Android 平台)檢索當前令牌,然後將目前令牌傳送到應用程式伺服器進行儲存(帶有時間戳記) )。這可能是配置為覆蓋所有客戶端/令牌的每月作業。 - 新增伺服器邏輯以定期更新令牌的時間戳,無論令牌是否已變更。
有關使用WorkManager更新令牌的 Android 邏輯範例,請參閱 Firebase 部落格上的管理雲端訊息傳遞令牌。
無論您遵循哪種計時模式,請確保定期更新令牌。每兩個月一次的更新頻率可能會在電池影響與檢測不活動註冊令牌之間取得良好的平衡。透過執行此刷新,您還可以確保任何處於非活動狀態的裝置在再次變為活動狀態時都會刷新其註冊。比每週更頻繁地進行刷新沒有任何好處。
刪除過時的註冊令牌
在向設備發送訊息之前,請確保設備註冊令牌的時間戳在您的過時視窗期內。例如,您可以實作 Cloud Functions for Firebase 來執行每日檢查,以確保時間戳位於定義的過時視窗期內,例如const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60;
然後刪除陳舊的令牌:
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(); });
});
取消訂閱主題中的過時令牌
管理主題訂閱以刪除過時的註冊令牌是另一個考慮因素。它涉及兩個步驟:
- 您的應用程式應每月和/或每當註冊令牌發生變化時重新訂閱主題。這形成了一個自我修復的解決方案,當應用程式再次啟動時,訂閱會自動重新出現。
- 如果應用程式實例閒置了 2 個月(或您自己的過時視窗),您應該使用Firebase Admin SDK取消訂閱它的主題,以從 FCM 後端刪除令牌/主題對應。
這兩個步驟的好處是,您的扇出將發生得更快,因為可供扇出的過時令牌較少,並且過時的應用程式實例一旦再次處於活動狀態,將自動重新訂閱。
衡量交付成功
一般來說,我們建議根據從活躍使用的應用程式實例中觀察到或捕獲的操作來定位訊息。如果您定期向擁有大量訂閱者的主題發送訊息,這一點尤其重要;如果這些訂戶中的一部分實際上不活躍,那麼隨著時間的推移,對您的遞送統計數據的影響可能會很大。
在將訊息定位到令牌之前,請考慮:
- Google Analytics、BigQuery 中擷取的資料或其他追蹤訊號是否表示令牌處於活動狀態?
- 之前的交付嘗試是否在一段時間內一直失敗?
- 過去兩個月內您的伺服器上的註冊令牌是否已更新?
- 對於 Android 設備, FCM 資料 API是否會報告由於
droppedDeviceInactive
導致的訊息傳送失敗比例較高?
有關傳遞的更多信息,請參閱了解訊息傳遞。