如果您使用 FCM API 以程式輔助方式建構傳送要求,可能會發現隨著時間推移,您傳送訊息給註冊資訊過時的閒置裝置,導致資源浪費。這種情況可能會影響 Firebase 控制台中回報的郵件送達資料,或匯出至 BigQuery 的資料,導致送達率大幅下降 (但實際上並非如此)。本指南將探討您可以採取的一些措施,確保訊息有效指定目標,並提供有效的傳送報表。
過時和過期的註冊
如果裝置超過一個月未連上 FCM,就會產生過時的註冊記錄。隨著時間經過,裝置連線至 FCM 的可能性會越來越低。這些過時的註冊項目不太可能收到訊息和主題扇出。
註冊資訊過時的原因有很多,舉例來說,與註冊程序相關聯的裝置可能遺失、損壞或存放起來,導致您忘記。
如果是 Android,當註冊項目閒置 270 天後,FCM會將其視為過期並進行垃圾收集。註冊過期後,FCM 會將其標示為無效,並拒絕傳送訊息。請注意,Firebase 安裝 ID (FID) 本身是由 Firebase Installations 服務 (FIS) 管理,而非 FCM。在極少數情況下,裝置會再次連線,且應用程式會在註冊項目遭到垃圾收集後開啟,此時用戶端應用程式會使用從 FIS 擷取的 FID,透過 FCM 再次註冊。請注意,FID 可能會變更;如要瞭解 FID 何時會重新發放,請參閱「管理 Firebase 安裝作業」。
對於 iOS 等其他平台,FCM 依賴於底層的推送服務 (例如 APNs),這類服務沒有基於閒置時間的 270 天效期。建議您主動維持註冊資訊的有效性,並移除過時的註冊資訊。
基本最佳做法
在任何使用 FCM API 以程式輔助方式建構傳送要求的應用程式中,您都應遵循一些基本做法。主要最佳做法如下:
- 從 FCM 擷取 Firebase 安裝 ID (FID),並儲存在應用程式伺服器上。伺服器的重要角色是追蹤每個用戶端註冊的 FID,並維護有效的 FID 清單。強烈建議您在資料庫中實作註冊時間戳記,並在每次上傳註冊資料時更新。
- 維持註冊資料的有效性,並移除過時的註冊資料。除了移除 FCM 不再視為有效的註冊項目,您可能還想監控其他註冊項目是否過時,並主動移除這些項目。本指南將說明幾種可達成此目標的方法。
擷取及儲存 Firebase 安裝 ID
應用程式首次啟動時,FCM SDK 會向 FCM 註冊應用程式執行個體,並傳回 Firebase 安裝 ID (FID)。這是您必須在 API 的目標傳送要求中加入的 ID,或用於主題訂閱。
強烈建議您在每次上傳 FID 時,一併將 FID 和時間戳記儲存至應用程式伺服器。更新每個上傳要求的時間戳記後,伺服器就能知道應用程式例項上次開啟的時間,以及與 FCM 後端成功同步的時間。
視自動初始化功能是否啟用 (包括不支援),您應按照下列方式處理註冊和更新:
- (建議) 啟用自動初始化功能:SDK 會自動保持註冊狀態,並監控變更。應用程式啟動時,系統會在例行同步處理期間定期叫用
onRegistered()回呼,FID 變更時也會叫用。只要實作這個回呼,即可將 FID 上傳至伺服器,並儲存目前的時間戳記。 - 停用自動初始化:系統不會在啟動時自動叫用
onRegistered()回呼。如要追蹤註冊情形並保持最新狀態,請在應用程式啟動時呼叫register(),例如在 Android 中,於主要活動的onCreate()呼叫。成功呼叫後,系統會使用 FID 觸發 FCM 註冊程序,並將 FID 傳送至onRegistered()回呼,讓應用程式上傳 FID 並更新伺服器上的時間戳記。
範例:Cloud Firestore 中的商店 FID 和時間戳記
舉例來說,您可以使用 Cloud Firestore 將 FID 儲存在名為 fcmRegistrations 的集合中。集合中的每個文件 ID 都對應至使用者 ID,且文件會儲存目前的 FID 和上次更新的時間戳記。使用如下 Kotlin 範例所示的 set 函式:
private fun sendRegistrationToServer(installationId: String?) {
// If you're running your own server, call API to send registration details and today's date for the user
// Example shown uses Firestore
// Add FID and timestamp to Firestore for this user
val deviceFid = hashMapOf(
"installationId" to installationId,
"timestamp" to FieldValue.serverTimestamp(),
)
// Get user ID from Firebase Auth or your own server
Firebase.firestore.collection("fcmRegistrations").document("myuserid")
.set(deviceFid)
}
每當 Firebase 安裝 ID 成功註冊或更新時,系統就會叫用 onRegistered() 回呼。您應實作這項回呼,上傳 FID 並更新時間戳記:
override fun onRegistered(installationId: String) {
Log.d(TAG, "Registered installation ID: $installationId")
// Send the Firebase Installation ID (FID) to your app server. Your app
// server should save the FID and update the timestamp upon receipt.
sendRegistrationToServer(installationId)
}
如果停用自動初始化,請在應用程式啟動時 (例如在 onCreate() 中) 呼叫 register(),透過 onRegistered() 觸發註冊流程並傳送 FID:
// Trigger manual registration if auto-initialization is turned off.
FirebaseMessaging.getInstance().register()
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// The registration callback onRegistered() will be invoked with the current FID.
} else {
Log.w(TAG, "Failed to register with Firebase Cloud Messaging", task.exception)
}
}
維持註冊資料的即時性,並移除過時的註冊資料
判斷註冊是否為新註冊或過時註冊,有時並不容易。為涵蓋所有情況,您應採用註冊過時的門檻。根據預設,如果應用程式執行個體一個月未連線,FCM 就會將註冊視為過時。如果註冊時間超過一個月,該裝置可能處於閒置狀態;否則有效裝置會重新註冊。
視用途而定,一個月可能太短或太長,因此請自行決定適合的條件。
偵測 FCM 後端傳回的無效回應
請務必偵測 FCM 的無效回應,並從系統中刪除已知無效或已過期的任何註冊。使用 HTTP v1 API 時,這些錯誤訊息可能表示傳送要求指定了無效或過期的註冊:
UNREGISTERED(HTTP 404)INVALID_ARGUMENT(HTTP 400)
如果您確定訊息酬載內容有效,且針對目標註冊收到其中一個回應,即可安全地刪除這項註冊記錄,因為該記錄永遠不會再有效。舉例來說,如要從 Cloud Firestore 刪除無效的註冊項目,您可以部署及執行下列函式:
// Firebase Installation ID comes from the client FCM SDKs
const firebaseInstallationId = 'YOUR_FIREBASE_INSTALLATION_ID';
const message = {
data: {
// Information you want to send inside of notification
},
fid: firebaseInstallationId
};
// Send message to device with provided Firebase Installation ID
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
})
.catch((error) => {
// Delete registration for user if error code is UNREGISTERED or INVALID_ARGUMENT.
if (error.errorCode == "messaging/registration-token-not-registered") {
// If you're running your own server, call API to delete the registration for the user
// Example shown uses Firestore
// Get user ID from Firebase Auth or your own server
Firebase.firestore.collection("fcmRegistrations").document(user.uid).delete()
}
});
如果 Android 裝置的註冊資訊在閒置 270 天後過期,或用戶端明確取消註冊,FCM 會傳回無效的回應。如要根據自己的定義更準確地追蹤過時狀態,可以主動移除過時的註冊內容。
定期更新註冊資訊
無論註冊是根據 FID 或舊版註冊權杖,伺服器都應在每次上傳要求時,更新資料庫中的註冊時間戳記。這個時間戳記會做為應用程式安裝的信號,讓用戶端知道應用程式已成功開啟並與 FCM 後端同步。請根據您使用的 API 實作適當策略:
Firebase 安裝 ID API (建議)
如果用戶端應用程式使用 FID API,則不需要在用戶端應用程式中排定定期背景工作,以擷取或重新整理註冊資料。在自動初始化期間,SDK 會自動處理重新整理作業,並在應用程式啟動期間的例行同步作業中,定期將正確的目前 FID 傳送至 onRegistered() 回呼。
如要讓伺服器保持在最新狀態,請實作「擷取及儲存 Firebase 安裝 ID」一文詳述的啟動上傳策略:
- 啟用自動初始化:SDK 會在應用程式啟動期間的例行同步作業中,自動確保將最新的 FID 傳送至伺服器。
- 自動初始化已停用或不支援:在應用程式啟動時 (例如在 Android 中,於主要活動的
onCreate()內) 呼叫register(),強制執行註冊序列,並觸發 FID 傳送至onRegistered()回呼。
這些策略可確保伺服器一律擁有最新的有效 FID,並能自動從上傳失敗的狀態復原,讓應用程式具備高度復原能力。
已淘汰的註冊權杖 API
如果您使用舊版註冊權杖,用戶端 SDK 不會在例行同步處理時自動管理重新整理作業。因此,建議您定期擷取及更新伺服器上的所有註冊權杖。請按照下列步驟操作:
- 在用戶端應用程式中新增應用程式邏輯,使用適當的 API 呼叫 (例如 Apple 平台的
token(completion):或 Android 的getToken()) 擷取目前權杖,然後將目前權杖傳送至應用程式伺服器進行儲存 (附上時間戳記)。這可能是設定為涵蓋所有用戶端或權杖的每月工作。 - 新增伺服器邏輯,定期更新權杖的時間戳記,無論權杖是否變更。
如需使用 WorkManager 更新舊版權杖的 Android 邏輯範例,請參閱 Firebase 網誌的「管理雲端通訊權杖」一文。
無論採用哪種時間模式,請務必定期更新權杖。每月更新一次的頻率,可兼顧電池續航力和偵測閒置註冊權杖。這樣做也能確保任何閒置裝置在恢復使用時,都會重新整理註冊資訊。如果重新整理的頻率超過每週一次,不會有任何好處。
移除過時的註冊資料
將訊息傳送至裝置前,請確認裝置註冊的時間戳記在過時時間範圍內。舉例來說,您可以實作 Cloud Functions for Firebase,每天執行檢查作業,確保時間戳記在定義的過時時間範圍 (例如 const
EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30;) 內,然後移除過時的註冊項目:
exports.pruneRegistrations = functions.pubsub.schedule('every 24 hours').onRun(async (context) => {
// Get all documents where the timestamp exceeds is not within the past month
const staleRegistrationsResult = await admin.firestore().collection('fcmRegistrations')
.where("timestamp", "<", Date.now() - EXPIRATION_TIME)
.get();
// Delete devices with stale registrations
staleRegistrationsResult.forEach(function(doc) { doc.ref.delete(); });
});
從主題取消訂閱過時的註冊項目
如果您使用主題,可能也想從訂閱的主題中取消註冊。這項程序包含兩個步驟:
- 每當 Firebase 安裝 ID (FID) 變更時,應用程式就應重新訂閱主題。這樣一來,應用程式再次啟用時,訂閱項目就會自動重新顯示。
- 如果應用程式執行個體閒置一個月 (或您自訂的過時時間範圍),您應使用 Firebase Admin SDK 取消訂閱主題,從 FCM 後端刪除 Firebase 安裝 ID 至主題的對應。
這兩個步驟的好處是,由於要散布的過時註冊資料較少,因此散布作業會更快完成,而且過時的應用程式例項會在再次啟用時自動重新訂閱。
評估傳送成功率
如要盡可能準確掌握訊息傳送情況,建議只將訊息傳送至正在使用的應用程式例項。如果您經常傳送訊息給大量訂閱者,這點就特別重要。如果這些訂閱者中有部分處於閒置狀態,長期下來,您的傳送統計資料可能會受到顯著影響。
將訊息指定至應用程式執行個體前,請先考量下列事項:
- Google Analytics、BigQuery 中擷取的資料或其他追蹤信號是否指出註冊程序已啟動?
- 先前嘗試傳送郵件時,是否持續一段時間都失敗?
- 過去一個月內,伺服器上的 Firebase 安裝 ID 是否已更新?
- 如果是 Android 裝置,FCM Data API 報告的訊息傳送失敗率是否偏高,且失敗原因為
droppedDeviceInactive?
如要進一步瞭解傳送作業,請參閱「瞭解訊息傳送作業」。