將訊息傳送至多部裝置

如要將訊息傳送至多部裝置,請使用主題訊息。這項功能可讓您將訊息傳送至已選擇特定主題的多部裝置。

本教學課程著重於使用 Admin SDKREST API,從應用程式伺服器傳送主題訊息至 FCM,並在 Android 應用程式中接收及處理這些訊息。我們會說明如何處理應用程式在背景和前景中收到的訊息。從設定到驗證,所有步驟都包含在內。

設定 SDK

如果您已設定 Android 用戶端應用程式以用於 FCM,或已完成傳送第一則訊息的步驟,本節可能包含您已完成的步驟。

事前準備

  • 安裝或更新 Android Studio 至最新版本。

  • 請確認專案符合下列規定 (請注意,部分產品可能會有更嚴格的規定):

    • 以 API 級別 21 (Lollipop) 以上版本為目標版本
    • 搭載 Android 5.0 以上版本
    • 使用 Jetpack (AndroidX),包括符合下列版本需求:
      • com.android.tools.build:gradle v7.3.0 以上版本
      • compileSdkVersion 28 以上版本
  • 設定實體裝置或使用模擬器執行應用程式。
    請注意,依附於 Google Play 服務的 Firebase SDK 必須在已安裝 Google Play 服務的裝置或模擬器上執行。

  • 使用 Google 帳戶登入 Firebase

如果您還沒有 Android 專案,只是想試用 Firebase 產品,可以下載我們的快速入門範例

建立 Firebase 專案

將 Firebase 加入 Android 應用程式前,請先建立要連結至該 Android 應用程式的 Firebase 專案。如要進一步瞭解 Firebase 專案,請參閱「瞭解 Firebase 專案」。

  1. Firebase 控制台中,按一下「新增專案」

    • 如要將 Firebase 資源新增至現有 Google Cloud 專案,請輸入專案名稱或從下拉式選單中選取專案。

    • 如要建立專案,請輸入專案名稱。您也可以選擇編輯專案名稱下方顯示的專案 ID。

  2. 如果系統顯示提示,請詳閱並接受 Firebase 條款

  3. 按一下「繼續」

  4. (選用) 為專案設定 Google Analytics,以便使用下列 Firebase 產品,獲得最佳體驗: Firebase A/B TestingCloud MessagingCrashlyticsIn-App MessagingRemote Config (包括個人化)。

    選取現有Google Analytics帳戶或建立新帳戶。如果建立新帳戶,請選取Analytics報表位置,然後接受專案的資料共用設定和Google Analytics條款。

  5. 按一下「建立專案」 (或「新增 Firebase」,如果要在現有的 Google Cloud 專案中新增 Firebase)。

Firebase 會自動為您的 Firebase 專案佈建資源。完成程序後,系統會將您帶往 Firebase 控制台的 Firebase 專案總覽頁面。

向 Firebase 註冊應用程式

如要在 Android 應用程式中使用 Firebase,請向 Firebase 專案註冊應用程式。註冊應用程式通常稱為「將應用程式新增至專案」。

  1. 前往 Firebase 控制台

  2. 在專案總覽頁面中間,按一下「Android」圖示 () 或「新增應用程式」,啟動設定工作流程。

  3. 在「Android 套件名稱」欄位中,輸入應用程式的套件名稱。

    • 套件名稱可明確識別裝置和 Google Play 商店中的應用程式。

    • 套件名稱通常稱為應用程式 ID

    • 在模組 (應用程式層級) Gradle 檔案中尋找應用程式的套件名稱,通常是 app/build.gradle (套件名稱範例:com.yourcompany.yourproject)。

    • 請注意,套件名稱值會區分大小寫,且在向 Firebase 專案註冊後,就無法變更這個 Firebase Android 應用程式的套件名稱。

  4. (選用) 輸入其他應用程式資訊:「應用程式暱稱」和「偵錯簽署憑證 SHA-1」

  5. 按一下 [Register app] (註冊應用程式)

新增 Firebase 設定檔

  1. 下載應用程式的 Firebase 設定檔 (google-services.json),然後新增至程式碼集:

    1. 按一下「下載 google-services.json」,取得應用程式的 Firebase 設定檔。

    2. 將設定檔移到應用程式的模組 (應用程式層級) 根目錄中。

    • Firebase 設定檔包含專案和應用程式的非祕密專屬 ID。如要進一步瞭解這個設定檔,請參閱「瞭解 Firebase 專案」。

    • 您隨時可以再次下載 Firebase 設定檔

    • 請確定設定檔名稱未附加額外的字元,例如 (2)

  2. 如要讓 Firebase SDK 存取 google-services.json 設定檔中的值,您需要Google 服務 Gradle 外掛程式 (google-services)。

    1. 根層級 (專案層級) 的 Gradle 檔案 (<project>/build.gradle.kts<project>/build.gradle) 中,將 Google 服務外掛程式新增為依附元件:

      KotlinGroovy
      plugins {
        id("com.android.application") version "7.3.0" apply false
        // ...
      
        // Add the dependency for the Google services Gradle plugin
        id("com.google.gms.google-services") version "4.4.3" apply false
      }
      plugins {
        id 'com.android.application' version '7.3.0' apply false
        // ...
      
        // Add the dependency for the Google services Gradle plugin
        id 'com.google.gms.google-services' version '4.4.3' apply false
      }
    2. 模組 (應用程式層級) Gradle 檔案 (通常為 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle) 中,加入 Google 服務外掛程式:

      KotlinGroovy
      plugins {
        id("com.android.application")
      
        // Add the Google services Gradle plugin
        id("com.google.gms.google-services")
        // ...
      }
      plugins {
        id 'com.android.application'
      
        // Add the Google services Gradle plugin
        id 'com.google.gms.google-services'
        // ...
      }

在應用程式中新增 Firebase SDK

  1. 模組 (應用程式層級) Gradle 檔案 (通常是 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle) 中,加入 Android 適用的 Firebase Cloud Messaging 程式庫依附元件。建議使用 Firebase Android BoM 控制程式庫版本。

    為獲得最佳 Firebase Cloud Messaging 體驗,建議您在 Firebase 專案中啟用 Google Analytics,並在應用程式中加入 Google Analytics 專用 Firebase SDK。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.16.0"))
    
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-messaging")
        implementation("com.google.firebase:firebase-analytics")
    }

    只要使用 Firebase Android BoM,應用程式就會一律使用相容的 Firebase Android 程式庫版本。

    如果選擇不使用 Firebase BoM,則必須在依附元件行中指定每個 Firebase 程式庫版本。

    請注意,如果應用程式使用多個 Firebase 程式庫,強烈建議使用 BoM 管理程式庫版本,確保所有版本都相容。

    dependencies {
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-messaging:24.1.2")
        implementation("com.google.firebase:firebase-analytics:22.5.0")
    }
    想尋找 Kotlin 專用的程式庫模組嗎?2023 年 10 月 (Firebase BoM 32.5.0) 起,Kotlin 和 Java 開發人員都可以依附於主要程式庫模組 (詳情請參閱這項計畫的常見問題)。

  2. 將 Android 專案與 Gradle 檔案同步。

    使用 Android Gradle 外掛程式 (AGP) 4.2 以下版本的 Gradle 建構作業,需要啟用 Java 8 支援。否則,這些 Android 專案在新增 Firebase SDK 時會發生建構失敗。

    如要修正這個建構失敗問題,可以採取下列任一做法:

    • 將錯誤訊息中列出的 compileOptions 新增至應用程式層級build.gradle.ktsbuild.gradle 檔案。
    • 將 Android 專案的 minSdk 提高至 26 以上。

    如要進一步瞭解建構失敗,請參閱這篇常見問題

讓用戶端應用程式訂閱主題

用戶端應用程式可以訂閱任何現有主題,也可以建立新主題。當用戶端應用程式訂閱新的主題名稱 (Firebase 專案中尚未存在的主題),系統會在 FCM 中建立該名稱的新主題,之後任何用戶端都能訂閱。

如要訂閱主題,用戶端應用程式會使用 FCM 主題名稱呼叫 Firebase Cloud Messaging subscribeToTopic()。這個方法會傳回 Task,完成事件監聽器可用於判斷訂閱是否成功:

KotlinJava
Firebase.messaging.subscribeToTopic("weather")
    .addOnCompleteListener { task ->
        var msg = "Subscribed"
        if (!task.isSuccessful) {
            msg = "Subscribe failed"
        }
        Log.d(TAG, msg)
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
    }
FirebaseMessaging.getInstance().subscribeToTopic("weather")
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                String msg = "Subscribed";
                if (!task.isSuccessful()) {
                    msg = "Subscribe failed";
                }
                Log.d(TAG, msg);
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
            }
        });

如要取消訂閱,用戶端應用程式會使用主題名稱呼叫 Firebase Cloud Messaging unsubscribeFromTopic()

接收及處理主題訊息

FCM 傳送主題訊息的方式與其他下游訊息相同。

如要接收訊息,請使用擴充 FirebaseMessagingService 的服務。 您的服務應覆寫 onMessageReceivedonDeletedMessages 回呼。

視呼叫 onMessageReceived 前發生的延遲情形 (包括 OS 延遲、應用程式啟動時間、主執行緒遭其他作業阻斷,或先前呼叫 onMessageReceived 花費過長時間),處理訊息的時間範圍可能短於 20 秒。之後,Android 的程序終止或 Android O 的 背景執行限制等各種 OS 行為,可能會干擾您完成工作。

onMessageReceived 適用於大多數郵件類型,但下列情況除外:

  • 應用程式在背景運作時傳送的通知訊息。在這種情況下,通知會傳送到裝置的系統匣。使用者輕觸通知時,系統預設會開啟應用程式啟動器。

  • 在背景接收同時含有通知和資料酬載的訊息。 在這種情況下,通知會傳送到裝置的系統匣,資料酬載則會傳送到啟動器活動意圖的額外項目。

簡單來說:

應用程式狀態 通知 資料 兩者並用
前景 onMessageReceived onMessageReceived onMessageReceived
背景 系統匣 onMessageReceived 通知:系統匣
資料:意圖的額外內容。
如要進一步瞭解訊息類型,請參閱通知和資料訊息

編輯應用程式資訊清單

如要使用 FirebaseMessagingService,請在應用程式資訊清單中新增下列內容:

<service
    android:name=".java.MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

此外,建議您設定預設值,自訂通知的外觀。您可以指定自訂預設圖示和自訂預設顏色,只要通知酬載中未設定對等值,系統就會套用這些預設值。

application 標記內新增下列程式碼,即可設定自訂預設圖示和自訂顏色:

<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
     See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_ic_notification" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
     notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

Android 會顯示自訂預設圖示,

  • 通知撰寫工具傳送的所有通知訊息。
  • 任何未在通知酬載中明確設定圖示的通知訊息。

Android 會將自訂預設顏色用於

  • 通知撰寫工具傳送的所有通知訊息。
  • 任何未在通知酬載中明確設定顏色的通知訊息。

如果未設定自訂預設圖示,且通知酬載中未設定圖示,Android 會顯示以白色算繪的應用程式圖示。

覆寫 onMessageReceived

覆寫 FirebaseMessagingService.onMessageReceived 方法,即可根據收到的 RemoteMessage 物件執行動作,並取得訊息資料:

KotlinJava
override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: ${remoteMessage.from}")

    // Check if message contains a data payload.
    if (remoteMessage.data.isNotEmpty()) {
        Log.d(TAG, "Message data payload: ${remoteMessage.data}")

        // Check if data needs to be processed by long running job
        if (needsToBeScheduled()) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob()
        } else {
            // Handle message within 10 seconds
            handleNow()
        }
    }

    // Check if message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());

        if (/* Check if data needs to be processed by long running job */ true) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob();
        } else {
            // Handle message within 10 seconds
            handleNow();
        }

    }

    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}

覆寫 onDeletedMessages

在某些情況下,FCM 可能無法傳送訊息。如果裝置連線時,應用程式在該裝置上待處理的訊息數量過多 (超過 100 則),或是裝置超過一個月未連線至 FCM,就會發生這種情況。在這些情況下,您可能會收到 FirebaseMessagingService.onDeletedMessages() 的回呼。應用程式執行個體收到這項回呼時,應與應用程式伺服器進行完整同步。如果過去 4 週內未透過該裝置傳送訊息給 FCMFCM 就不會撥打電話給 onDeletedMessages()

處理背景應用程式中的通知訊息

當應用程式在背景執行時,Android 會將通知訊息導向系統匣。使用者輕觸通知後,系統預設會開啟應用程式啟動器。

包括同時含有通知和資料酬載的訊息 (以及從「通知」控制台傳送的所有訊息)。在這些情況下,通知會傳送到裝置的系統匣,資料酬載則會傳送到啟動器 Activity 的 Intent 額外內容中。

如要深入瞭解訊息傳送至應用程式的情況,請參閱 FCM報表資訊主頁,其中會記錄在 Apple 和 Android 裝置上傳送及開啟的訊息數量,以及 Android 應用程式的「曝光次數」(使用者看到的通知) 資料。

建立傳送要求

建立主題後 (方法是在用戶端訂閱主題,或透過伺服器 API),即可將訊息傳送至該主題。如果您是第一次為 FCM 建立傳送要求,請參閱伺服器環境和 FCM 的指南,瞭解重要的背景和設定資訊。

在後端的傳送邏輯中,指定所需的主題名稱,如下所示:

// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';

const message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";

// See documentation on defining a message payload.
Message message = Message.builder()
    .putData("score", "850")
    .putData("time", "2:45")
    .setTopic(topic)
    .build();

// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'

# See documentation on defining a message payload.
message = messaging.Message(
    data={
        'score': '850',
        'time': '2:45',
    },
    topic=topic,
)

# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Topic: topic,
}

// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";

// See documentation on defining a message payload.
var message = new Message()
{
    Data = new Dictionary<string, string>()
    {
        { "score", "850" },
        { "time", "2:45" },
    },
    Topic = topic,
};

// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
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":{
    "topic" : "foo-bar",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message"
      }
   }
}

cURL 指令:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "message": {
    "topic" : "foo-bar",
    "notification": {
      "body": "This is a Firebase Cloud Messaging Topic Message!",
      "title": "FCM Message"
    }
  }
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

如要將訊息傳送至主題組合,請指定條件,也就是指定目標主題的布林運算式。舉例來說,下列條件會將訊息傳送至已訂閱 TopicA,且訂閱 TopicBTopicC 的裝置:

"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"

FCM 會先評估括號中的任何條件,然後從左到右評估運算式。在上述運算式中,訂閱任何單一主題的使用者都不會收到訊息。同樣地,如果使用者未訂閱 TopicA,就不會收到訊息。以下組合會收到這項功能:

  • TopicATopicB
  • TopicATopicC

條件式運算式最多可包含五個主題。

如要傳送至條件:

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';

// See documentation on defining a message payload.
const message = {
  notification: {
    title: '$FooCorp up 1.43% on the day',
    body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
  },
  condition: condition
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
Message message = Message.builder()
    .setNotification(Notification.builder()
        .setTitle("$GOOG up 1.43% on the day")
        .setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
        .build())
    .setCondition(condition)
    .build();

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"

# See documentation on defining a message payload.
message = messaging.Message(
    notification=messaging.Notification(
        title='$GOOG up 1.43% on the day',
        body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
    ),
    condition=condition,
)

# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Condition: condition,
}

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
var message = new Message()
{
    Notification = new Notification()
    {
        Title = "$GOOG up 1.43% on the day",
        Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
    },
    Condition = condition,
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
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":{
    "condition": "'dogs' in topics || 'cats' in topics",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message",
    }
  }
}

cURL 指令:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "FCM Message",
    "body": "This is a Firebase Cloud Messaging Topic Message!",
  },
  "condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

後續步驟