將訊息傳送至多部裝置

如要指定訊息給多部裝置,請使用 主題訊息:這個 功能 傳送訊息給已選擇加入特定主題的多部裝置。

本教學課程著重於使用如何從應用程式伺服器傳送主題訊息: Admin SDK 或 適用於 FCM 的 REST API 以及接收與處理 請在 Android 應用程式中使用 API我們將說明背景與背景訊息的處理方式 在應用程式前景顯示。我們將從設定到 驗證。

設定 SDK

本節可能會涵蓋您已完成的步驟, 已設定 Android 用戶端應用程式 或已逐步完成以下作業: 傳送第一則訊息

事前準備

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

  • 請確認您的專案符合這些規定 (請注意部分產品 可能有更嚴格的規定):

    • 目標 API 級別 19 (KitKat) 或更高
    • 使用 Android 4.4 以上版本
    • 用途 Jetpack (AndroidX)、 符合以下版本需求:
      • com.android.tools.build:gradle 7.3.0 以上版本
      • compileSdkVersion 28 以上版本
  • 設定實體裝置或使用 模擬器 執行應用程式。
    請注意,在 Google Play 上具有依附元件的 Firebase SDK 服務需要裝置或 安裝 Google Play 服務的模擬器。

  • 使用 Google 帳戶登入 Firebase 讓他們使用服務帳戶

如果您還沒有 Android 專案,只想試用 Firebase 產品,您可以下載其中一個快速入門導覽課程範例

建立 Firebase 專案

您需要先建立 Firebase,才能將 Firebase 新增到你的 Android 應用程式 連線至 Android 應用程式前往 如要進一步瞭解 Firebase 專案,請參閱這篇文章 Firebase 專案。

透過 Firebase 註冊應用程式

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

  1. 前往 Firebase 主控台

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

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

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

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

新增 Firebase 設定檔

  1. 下載並新增 Firebase Android 設定檔 (google-services.json) 給您的應用程式:

    1. 點選「Download google-services.json」,取得 Firebase Android 設定檔。

    2. 將設定檔移至 module (app-level) 根目錄中

  2. google-services.json 設定檔中的值可供存取 導入 Firebase SDK,則需要 Google 服務 Gradle 外掛程式 (google-services)。

    1. 在您的根層級 (專案層級) Gradle 檔案 (<project>/build.gradle.kts<project>/build.gradle),請將 以依附元件的形式 Google 服務外掛程式:

      Kotlin

      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.2" apply false
      }
      

      Groovy

      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.2' apply false
      }
      
    2. 模組 (應用程式層級) Gradle 檔案中 (通常為 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle)、 新增 Google 服務外掛程式:

      Kotlin

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

      Groovy

      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 雲端通訊程式庫的依附元件。建議您使用 Firebase Android BoM 管理程式庫版本管理

    為獲得最佳 Firebase 雲端通訊體驗,建議你 啟用 Google Analytics ,然後將 Google Analytics 專用 Firebase SDK 加進應用程式。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
    
        // 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 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.0.0")
        implementation("com.google.firebase:firebase-analytics:22.0.2")
    }
    
    在尋找 Kotlin 專用的程式庫模組嗎?距離開始還有 2023 年 10 月 (Firebase BoM 32.5.0),Kotlin 和 Java 開發人員都能 依附於主要程式庫模組 (詳情請參閱 這項計畫的常見問題)。

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

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

用戶端應用程式可以訂閱任何現有的主題,也可以建立新的主題 主題。用戶端應用程式訂閱新的主題名稱時 (具有 Firebase 專案尚不存在的新主題,但這個名稱的新主題會是 任何透過 FCM 建立且任何客戶都能訂閱的。

為了訂閱主題,用戶端應用程式會呼叫 Firebase 雲端通訊 將 subscribeToTopic() 替換為 FCM 主題名稱。這個方法 會傳回 Task,完成事件監聽器可用於判斷 訂閱成功:

Kotlin+KTX

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()
    }

Java

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 雲端通訊 unsubscribeFromTopic() 替換成主題名稱

接收及處理主題訊息

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

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

郵件處理時長可能短於 20 秒,視延遲情況而定 在呼叫 onMessageReceived 之前發生,包括 OS 延遲、應用程式啟動時間、 主要執行緒遭到其他作業封鎖 (或先前的 onMessageReceived) 通話時間過長。超過 190 天後,各種作業系統行為,如 Android 的 處理程序 終止或 Android O 的 背景執行限制可能會影響無法完成工作。

大多數訊息類型都會提供 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 物件,並取得訊息資料:

Kotlin+KTX

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.
}

Java

@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) 待審核 應用程式在連線的特定裝置上,或裝置未連線時 已有超過 1 個月啟用 FCM。在這些情況下 系統可能會回撥給 FirebaseMessagingService.onDeletedMessages() 應用程式執行個體收到此回呼時 應該能與應用程式伺服器執行完整同步。如果有沒有傳送訊息給應用程式 裝置在過去 4 週內,FCM 將不會呼叫 onDeletedMessages()

在背景執行應用程式中處理通知訊息

當應用程式在背景運作時,Android 會將通知訊息導向至 系統匣。使用者輕觸通知,即可開啟應用程式啟動器 預設值。

這包括包含通知和資料的訊息 酬載 (以及從通知控制台傳送的所有郵件)。 在這些情況下,通知會傳送到裝置的 系統匣,而資料酬載則會在意圖的額外項目中傳送 。

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

版本傳送要求

建立主題後,您可以透過訂閱用戶端應用程式執行個體 在用戶端或 server API 進行連線,您就能傳送訊息至 主題。如果您是第一次建立 FCM 要求,請 請參閱 伺服器環境和 FCM 重要的背景和設定資訊。

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

Node.js

// 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);
  });

Java

// 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);

Python

# 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)

Go

// 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)

C#

// 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);

REST

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

如要傳送訊息給主題的組合, 指定條件,這是一種布林值運算式,可以指定 指定主題。舉例來說,當郵件符合下列條件時, 訂閱 TopicATopicBTopicC 的裝置:

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

FCM 會先評估括號中的任何條件,然後進行評估 運算式則從左到右依序為在上述運算式中,有一位使用者訂閱 任何主題都不會收到訊息。同樣地,如果使用者未 訂閱 TopicA 並未收到訊息。這些組合 收到:

  • TopicATopicB
  • TopicATopicC

條件運算式最多可加入五個主題。

如要傳送至條件:

Node.js

// 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);
  });

Java

// 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);

Python

# 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)

Go

// 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)

C#

// 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);

REST

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

後續步驟