如果您使用 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
導致訊息傳送失敗的比例偏高?
如要進一步瞭解傳送,請參閱「瞭解訊息傳送」。