向多個設備發送消息

若要將訊息定位到多個設備,請使用主題訊息傳遞。此功能可讓您向選擇加入特定主題的多個裝置發送訊息。

本教學重點在於使用適用於 FCM 的Admin SDKREST API從應用程式伺服器發送主題訊息,以及在 Android 應用程式中接收和處理它們。我們將介紹後台和前台應用程式的訊息處理。涵蓋了實現這一目標的所有步驟,從設定到驗證。

設定SDK

如果您已設定 Android 用戶端應用程式或完成了發送第一則訊息的步驟,本部分可能會介紹您已完成的步驟。

在你開始之前

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

  • 確保您的專案符合以下要求:

    • 目標 API 等級 19 (KitKat) 或更高
    • 使用Android 4.4或更高版本
    • 使用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 應用程式之前,您需要建立一個 Firebase 專案來連接到您的 Android 應用程式。請造訪了解 Firebase 專案以了解有關 Firebase 專案的更多資訊。

向 Firebase 註冊您的應用

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

  1. 轉到Firebase 控制台

  2. 在專案概述頁面的中心,按一下Android圖示 ( ) 或新增應用程式以啟動設定工作流程。

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

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

  5. 點擊註冊應用程式

新增 Firebase 設定檔

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

    1. 點擊下載 google-services.json以取得您的 Firebase Android 設定檔。

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

  2. 要讓 Firebase SDK 可以存取google-services.json設定檔中的值,您需要Google services 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.1" 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.1' 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 )中,新增 Firebase Cloud 的依賴項Android 訊息傳遞庫。我們建議使用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:32.7.4"))
    
        // 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 程式庫的相容版本。

    (替代方法)在不使用 BoM 的情況下新增 Firebase 庫依賴項

    如果您選擇不使用 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:23.4.1")
        implementation("com.google.firebase:firebase-analytics:21.5.1")
    }
    
    正在尋找 Kotlin 特定的庫模組?2023 年 10 月(Firebase BoM 32.5.0)開始,Kotlin 和 Java 開發人員都可以依賴主庫模組(有關詳細信息,請參閱有關此計劃的常見問題解答)。

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

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

客戶端應用程式可以訂閱任何現有主題,也可以建立新主題。當用戶端套用訂閱新主題名稱(您的 Firebase 專案尚不存在的主題)時,會在 FCM 中建立具有該名稱的新主題,並且隨後任何客戶端都可以訂閱該主題。

要訂閱主題,用戶端應用程式使用 FCM 主題名稱呼叫 Firebase Cloud Messaging subscribeToTopic() 。此方法傳回一個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 Cloud Messaging unsubscribeFromTopic()

接收並處理主題訊息

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

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

處理訊息的時間視窗可能會短於 20 秒,具體取決於在呼叫onMessageReceived之前發生的延遲,包括作業系統延遲、應用程式啟動時間、主執行緒被其他操作阻塞或之前的onMessageReceived呼叫花費太長時間。此後,各種作業系統行為(例如 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 應用程式的「印象數」(使用者看到的通知)數據。

建構發送請求

建立主題後,您可以透過在客戶端訂閱該主題的客戶端應用程式實例或透過伺服器 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);
  });

爪哇

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

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

休息

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

您最多可以在條件表達式中包含五個主題。

發送到條件:

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

爪哇

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

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

休息

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

下一步