從 FCM HTTP v1 API 遷移至 AndroidConfigV2

使用 FCM AndroidConfigV2,您可以傳送意圖導向訊息至 Android 裝置,並及時傳送通知和簡化 API。

本指南說明如何將後端傳送要求從 AndroidConfig 遷移至 AndroidConfigV2,以及如何將 Android 應用程式用戶端從舊版 onMessageReceived 處理方式更新為新的第 2 版用戶端回呼 onNotificationReceived 和 onBackgroundSyncReceived。

AndroidConfigV2 的新功能

FCM AndroidConfigV2 API 推出兩種訊息類型,簡化開發人員體驗:

  • RemoteNotification:建議用於所有顯示緊急、具時效性且使用者可見通知的 FCM 訊息。這類訊息會略過所有省電措施,並立即傳送。在裝置上,FCM SDK 和 Google Play 服務會負責將通知發布至 Android 的 NotificationManager。
  • BackgroundSync:非緊急資料訊息,用於背景同步處理和應用程式狀態更新。系統可能會批次或延遲傳送,以盡可能延長電池續航力。
功能 舊版 (AndroidConfig 第 1 版) 新 (AndroidConfigV2)
設定欄位 message.android message.android_v2
優先檔案區定義 明確的 priority: "high" 或 priority: "normal" 由聯合型別暗示 (remote_notification 用於緊急警示,background_sync 用於非緊急同步)
通知發布 應用程式手動呼叫 NotificationManager.notify() FCM SDK 和 GMS Core 直接或使用 NotificationResponse 發布
用戶端回呼 • onMessageReceived(RemoteMessage)
• onDeletedMessages()
• onNotificationReceived(RemoteMessage)
• onBackgroundSyncReceived(RemoteMessage)
• onDeletedMessages()
可收合性 不一;必須明確設定 這兩種訊息類型都可以是可收合或不可收合 (預設為不可收合)

授權 AndroidConfigV2 傳送要求

您可以繼續使用透過 Google 應用程式預設憑證或 Admin SDK,從 Firebase 服務帳戶衍生的 OAuth 2.0 存取權杖,並將 POST 要求傳送至 HTTP v1 messages:send 端點。

如果不是使用 Firebase 服務帳戶,請務必將專用帳戶指派給 Firebase 雲端通訊 API 管理員角色 (roles/firebase.messagingAdmin),並在建立存取權杖時要求 https://www.googleapis.com/auth/firebase.messaging OAuth 2.0 範圍:

POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send HTTP/1.1
Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN
Content-Type: application/json

更新伺服器傳送要求 (REST)

如要使用 REST API 傳送 AndroidConfigV2 要求,請將要求主體中的 android 物件替換為 android_v2。在 android_v2 中,指定常用參數 (例如 ttl、collapse_key、data 和 restricted_package_name),並提供下列其中一個聯集欄位:remote_notification 或 background_sync。

傳送 RemoteNotification 訊息

如要傳送緊急的面向使用者通知,請加入 remote_notification 物件和建議的 notification 酬載。mutable_content 預設為 false。GMS Core 會立即轉譯通知並發布到系統匣,不會執行背景應用程式程式碼。

完整 HTTP 要求 JSON 範例 (自動算繪通知)

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification": {
      "title": "FCM Message",
      "body": "This is an FCM notification message!"
    },
    "android_v2": {
      "ttl": "3600s",
      "remote_notification": {
        "mutable_content": false,
        "notification": {
          "title": "Order update",
          "body": "Your order has been shipped!",
          "icon": "ic_notification_custom",
          "sound": "default",
          "tag": "alert_tag_1",
          "id": 100
        }
      }
    }
  }
}

傳送含有可變內容的 RemoteNotification

如果應用程式需要在裝置上顯示通知前修改通知,請在 remote_notification 中設定 mutable_content: true。這會指示 GMS Core 繫結至用戶端應用程式,並叫用 onNotificationReceived()。如要解密端對端加密酬載、附加使用者顯示圖片或本地化字串,這項功能就非常實用。

即使 mutable_content 為 true,也請務必在 remote_notification 中加入 notification 物件。如果用戶端回呼逾時或超過抑制限制,GMS Core 會使用這個 notification 物件做為備用通知。

如果您是從舊版 v1 資料訊息遷移,且希望 FCM 將酬載以純資料訊息的形式傳送至舊版 v1 用戶端安裝,請在 remote_notification 內設定 use_as_v1_data_message: true。

完整的 HTTP 要求 JSON 範例

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data": {
      "encrypted_content": "U2FsdGVkX19vN2m+",
      "sender_id": "user_98124"
    },
    "android_v2": {
      "ttl": "3600s",
      "remote_notification": {
        "mutable_content": true,
        "use_as_v1_data_message": true,
        "notification": {
          "title": "New message",
          "body": "You have a new message",
          "tag": "chat_alert",
          "id": 1001
        }
      }
    }
  }
}

傳送 BackgroundSync 訊息

如要傳送非緊急訊息,用於背景資料同步、靜音狀態更新,或更新應用程式徽章或小工具,請使用 background_sync 物件。請勿使用這類訊息發布使用者可見的通知,因為這類訊息可能會批次處理或延遲,以最佳化裝置電池續航力。

完整的 HTTP 要求 JSON 範例

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data": {
      "score": "FETCH_UPDATES",
      "sync_action": "FETCH_UPDATES"
    },
    "android_v2": {
      "ttl": "86400s",
      "collapse_key": "app_state_sync",
      "background_sync": {}
    }
  }
}

更新 Android 用戶端回呼

如要在 Android 上接收AndroidConfigV2訊息,請升級應用程式,改用最新版 firebase-messaging SDK。請實作 onNotificationReceived() 和 onBackgroundSyncReceived(),更新擴充 FirebaseMessagingService 的服務。

安裝 EAP SDK

在 EAP 期間,firebase-messaging v2 SDK 會以本機 Maven 存放區封存檔的形式發布,而非透過 Google 的公開 Maven 存放區。

  1. 使用下列任一選項,將包含 firebase-messaging:26.1.0-eap01 的 m2repository 目錄 新增至 Android 專案的根目錄 (<project-root>/m2repository) :

    1. 方法 A:下載預先建構的封存檔:下載 FirebaseMessaging_26.1.0-eap01.zip,然後將 m2repository 目錄解壓縮至 Android 專案的根目錄 (<project-root>/m2repository)。
    2. 方法 B:使用 Gradle 從 GitHub 建構:複製 firebase/firebase-android-sdk 的 feature/fcm-android-config-v2-eap 分支版本、建構本機 Maven 存放區,然後將產生的 build/m2repository 目錄複製到 Android 專案根目錄:

      git clone --branch feature/fcm-android-config-v2-eap --single-branch \
        https://github.com/firebase/firebase-android-sdk.git
      cd firebase-android-sdk
      ./gradlew -PprojectsToPublish="firebase-messaging" firebasePublish
      cp -R build/m2repository /path/to/your/android-project/m2repository
      
  2. 在專案層級的 settings.gradle.kts (或 settings.gradle) 中,將本機 m2repository 目錄新增至 repositories 區塊頂端,位於 google() 上方:

    Kotlin

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { url = uri("${rootDir}/m2repository") }
            google()
            mavenCentral()
        }
    }
    

    Groovy

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { url "${rootDir}/m2repository" }
            google()
            mavenCentral()
        }
    }
    
  3. 在模組層級 app/build.gradle.kts (或 app/build.gradle) 中,將 firebase-messaging 依附元件更新為 26.1.0-eap01:

    Kotlin

    dependencies {
        implementation("com.google.firebase:firebase-messaging:26.1.0-eap01")
    }
    

    Groovy

    dependencies {
        implementation 'com.google.firebase:firebase-messaging:26.1.0-eap01'
    }
    
  4. 將專案與 Gradle 檔案同步處理。

使用 onNotificationReceived 處理 RemoteNotifications

使用 AndroidConfigV2 時,除非 mutable_content 設為 true,或在 onNotificationReceived() 中明確設為靜音,否則系統會顯示 RemoteNotification 訊息。

當 RemoteNotification 帶著 mutable_content: true 抵達時,GMS Core 會繫結至您的應用程式並呼叫 onNotificationReceived()。方法必須在 20 秒內傳回 NotificationResponse。

傳回修改後的通知

您傳遞至 NotificationResponse.create 的 Notification 物件會取代傳送要求中定義的伺服器 remote_notification.notification 備用酬載屬性。如要保留傳送要求中的值,請在套用裝置端修改時,從 message.notification (Java 中的 message.getNotification()) 讀取這些值:

Kotlin

import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.NotificationResponse
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
        val serverNotif = message.notification
        val channelId = serverNotif?.channelId ?: "default_channel"
        // Keep the server-defined title unchanged, or fall back to default
        val title = serverNotif?.title ?: "New message"
        // Modify the notification body on-device (for example, decrypting data)
        val decryptedBody = decryptPayload(message.data["encrypted_content"])

        val builder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_stat_notification)
            .setContentTitle(title)
            .setContentText(decryptedBody)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)

        // Return the response for the open-source FCM SDK to post to the Android platform
        return NotificationResponse.create("optional_tag", 100, builder.build())
    }
}

Java

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.firebase.messaging.NotificationResponse;
import androidx.core.app.NotificationCompat;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    @Override
    public NotificationResponse onNotificationReceived(RemoteMessage message) {
        RemoteMessage.Notification serverNotif = message.getNotification();
        String channelId = (serverNotif != null && serverNotif.getChannelId() != null)
                ? serverNotif.getChannelId()
                : "default_channel";
        // Keep the server-defined title unchanged, or fall back to default
        String title = (serverNotif != null && serverNotif.getTitle() != null)
                ? serverNotif.getTitle()
                : "New message";
        // Modify the notification body on-device (for example, decrypting data)
        String decryptedBody = decryptPayload(message.getData().get("encrypted_content"));

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.drawable.ic_stat_notification)
                .setContentTitle(title)
                .setContentText(decryptedBody)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true);

        // Return the response for the open-source FCM SDK to post to the Android platform
        return NotificationResponse.create("optional_tag", 100, builder.build());
    }
}

隱藏通知

如果使用者目前正在使用相關畫面或聊天室,您可以傳回 NotificationResponse.suppress() 來隱藏通知:

Kotlin

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner

override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
    val chatId = message.data["chat_id"]
    val isAppForeground = ProcessLifecycleOwner.get()
        .lifecycle
        .currentState
        .isAtLeast(Lifecycle.State.STARTED)

    if (isAppForeground && chatRepository.isConversationActive(chatId)) {
        // Suppress notification display because the user is actively viewing this chat
        return NotificationResponse.suppress()
    }

    val builder = buildNotificationForChat(message)
    return NotificationResponse.create("chat_tag", 101, builder.build())
}

Java

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ProcessLifecycleOwner;

@Override
public NotificationResponse onNotificationReceived(RemoteMessage message) {
    String chatId = message.getData().get("chat_id");
    boolean isAppForeground = ProcessLifecycleOwner.get()
            .getLifecycle()
            .getCurrentState()
            .isAtLeast(Lifecycle.State.STARTED);

    if (isAppForeground && chatRepository.isConversationActive(chatId)) {
        // Suppress notification display because the user is actively viewing this chat
        return NotificationResponse.suppress();
    }

    NotificationCompat.Builder builder = buildNotificationForChat(message);
    return NotificationResponse.create("chat_tag", 101, builder.build());
}

串流通知或取消舊警報 (FcmNotificationManager)

如需針對單一傳入推播發布多則通知,或在顯示新通知前取消過時通知,請使用 FcmNotificationManager:

Kotlin

override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
    // Retrieve the scoped manager for this message execution
    val manager = getFcmNotificationManager(message)

    // 1. Cancel an outdated notification right away
    manager.cancelNow("message_tag", 50)

    // 2. Post the item notification immediately
    val itemNotif = NotificationCompat.Builder(this, "chat_channel")
        .setSmallIcon(R.drawable.ic_message)
        .setContentTitle("Alex")
        .setContentText(message.data["text"])
    manager.postNow("item_tag", 51, itemNotif.build())

    // 3. Post a group summary notification
    val summaryNotif = NotificationCompat.Builder(this, "chat_channel")
        .setSmallIcon(R.drawable.ic_message)
        .setContentTitle("New Conversations")
        .setGroupSummary(true)
    manager.postNow("summary_tag", 52, summaryNotif.build())

    // 4. Return finished() to complete the streaming workflow
    return manager.finished()
}

Java

@Override
public NotificationResponse onNotificationReceived(RemoteMessage message) {
    // Retrieve the scoped manager for this message execution
    FcmNotificationManager manager = getFcmNotificationManager(message);

    // 1. Cancel an outdated notification right away
    manager.cancelNow("message_tag", 50);

    // 2. Post the item notification immediately
    NotificationCompat.Builder itemNotif = new NotificationCompat.Builder(this, "chat_channel")
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle("Alex")
            .setContentText(message.getData().get("text"));
    manager.postNow("item_tag", 51, itemNotif.build());

    // 3. Post a group summary notification
    NotificationCompat.Builder summaryNotif = new NotificationCompat.Builder(this, "chat_channel")
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle("New Conversations")
            .setGroupSummary(true);
    manager.postNow("summary_tag", 52, summaryNotif.build());

    // 4. Return finished() to complete the streaming workflow
    return manager.finished();
}

使用 onBackgroundSyncReceived 處理 BackgroundSync 訊息

如要在 Android 上處理 BackgroundSync 訊息,請使用 onBackgroundSyncReceived():

Kotlin

override fun onBackgroundSyncReceived(message: RemoteMessage) {
    val syncCommand = message.data["sync_action"]

    try {
        // Perform local database synchronization or cache refresh
        AppDataRepository.getInstance(applicationContext).syncWithServer(syncCommand)
    } catch (e: Exception) {
        Log.e("FCM", "Background sync operation failed", e)
    }
    // Execution completes -> GMS Core marks the message as handled and unbinds.
}

Java

@Override
public void onBackgroundSyncReceived(RemoteMessage message) {
    String syncCommand = message.getData().get("sync_action");

    try {
        // Perform local database synchronization or cache refresh
        AppDataRepository.getInstance(getApplicationContext()).syncWithServer(syncCommand);
    } catch (Exception e) {
        Log.e("FCM", "Background sync operation failed", e);
    }
    // Execution completes -> GMS Core marks the message as handled and unbinds.
}

使用 onDeletedMessages 處理待刪除的訊息

在某些情況下,例如裝置長時間未連線至 FCM,或伺服器佇列已滿,FCM 可能會在傳送前刪除待處理訊息。在這種情況下,請覆寫 onDeletedMessages():

Kotlin

override fun onDeletedMessages() {
    // Perform a full state synchronization with your app server
    AppDataRepository.getInstance(applicationContext).scheduleFullSync()
}

Java

@Override
public void onDeletedMessages() {
    // Perform a full state synchronization with your app server
    AppDataRepository.getInstance(getApplicationContext()).scheduleFullSync();
}

遷移特定應用程式

傳送端對端加密 (E2EE) 通知

使用 RemoteNotification 傳送端對端加密通知時,解密的通知內容會保留在應用程式和 Android 平台中:

  1. 傳送可變動通知:伺服器會傳送 RemoteNotification,其中包含 mutable_content: true、data 中的加密酬載,以及一般未加密備份 notification。GMS Core 會將加密訊息傳送至應用程式中的開放原始碼 FCM SDK。
  2. 解密並傳回通知:FCM SDK 會叫用 onNotificationReceived()。應用程式會在本機解密酬載,建構最終解密的 Android Notification,並使用 NotificationResponse 或 FcmNotificationManager 將其傳回開放原始碼 FCM SDK。
  3. 發布至平台並回報中繼資料:開放原始碼 FCM SDK 會直接將解密的通知發布至 Android 平台 NotificationManager API,並僅將通知中繼資料 (例如通知 ID 和時間戳記) 傳回 GMS Core,以便觀測傳送情形。解密後的內容絕不會傳送至 GMS Core。
  4. 視需要發布備份通知:只有在應用程式未在 20 秒逾時內回應,或超出背景抑制限制時,GMS Core 才會直接將一般備份 notification 發布至 Android 平台。

架構圖:顯示 GMS Core、開放原始碼 FCM SDK、應用程式和 Android 平台 NotificationManager API 之間,4 個步驟的端對端加密通知傳送流程。

瞭解通知抑制和逾時

為確保 Android 生態系統中的訊息傳遞可靠,GMS Core 在處理 RemoteNotification 訊息時,會套用處理逾時和通知抑制限制:

處理逾時

如果 mutable_content 設為 true,onNotificationReceived() 方法必須在 20 秒內傳回 NotificationResponse。如果執行時間超過這個門檻,系統會取消回呼,GMS Core 則會自動發布從 remote_notification.notification 酬載轉譯的備用通知。

通知抑制率

由於 RemoteNotification 訊息會略過 Doze 限制,立即提醒使用者並立即傳送,因此應用程式應會顯示收到的 RemoteNotification 訊息通知。

每個應用程式在背景執行時,最多可抑制 25% 的 RemoteNotification 流量。如果應用程式的抑制率超過 25%,GMS Core 會自動顯示並發布從 remote_notification.notification 酬載轉譯的備用通知。

應用程式在前景執行時,系統不會將抑制次數計入這項門檻。此外,如果應用程式獲授予不受限制的背景使用權限,也不會受到背景抑制限制。