Firebase Cloud Messaging 提供了兩種將消息發送到多個設備的方法:
本教程重點介紹使用Admin SDK或適用於 FCM 的REST API從您的應用服務器發送主題消息,並在 Android 應用中接收和處理它們。我們將介紹後台和前台應用程序的消息處理。涵蓋了實現這一目標的所有步驟,從設置到驗證。
設置開發工具包
如果您已為 FCM設置了 Android 客戶端應用程序或完成了發送您的第一條消息的步驟,則此部分可能涵蓋您已完成的步驟。
在你開始之前
安裝或更新Android Studio到其最新版本。
確保您的項目滿足以下要求:
- 目標 API 級別 19 (KitKat) 或更高級別
- 使用 Android 4.4 或更高版本
- 使用Jetpack (AndroidX) ,其中包括滿足以下版本要求:
-
com.android.tools.build:gradle
v3.2.1 或更高版本 compileSdkVersion
28 或更高版本
-
設置物理設備或使用模擬器來運行您的應用程序。
請注意,依賴於 Google Play 服務的 Firebase SDK要求設備或模擬器安裝 Google Play 服務。使用您的 Google 帳戶登錄 Firebase 。
如果您還沒有 Android 項目並且只想試用 Firebase 產品,您可以下載我們的快速入門示例之一。
創建一個 Firebase 項目
在將 Firebase 添加到您的 Android 應用程序之前,您需要創建一個 Firebase 項目以連接到您的 Android 應用程序。訪問了解 Firebase 項目以了解有關 Firebase 項目的更多信息。
向 Firebase 註冊您的應用
要在您的 Android 應用程序中使用 Firebase,您需要向您的 Firebase 項目註冊您的應用程序。註冊您的應用程序通常稱為將您的應用程序“添加”到您的項目中。
轉到Firebase 控制台。
在項目概覽頁面的中央,單擊Android圖標 (
) 或添加應用程序以啟動設置工作流程。在Android 程序包名稱字段中輸入您的應用程序的程序包名稱。
包名稱在設備和 Google Play 商店中唯一標識您的應用程序。
包名稱通常稱為應用程序 ID 。
在模塊(應用程序級)Gradle 文件中找到應用程序的包名稱,通常
app/build.gradle
(例如包名稱:com.yourcompany.yourproject
)。請注意,包名稱值區分大小寫,並且在您的 Firebase 項目中註冊後,無法更改此 Firebase Android 應用程序。
(可選)輸入其他應用信息:應用暱稱和調試簽名證書 SHA-1 。
應用暱稱:僅在 Firebase 控制台中對您可見的內部便利標識符
調試簽名證書 SHA-1 :Firebase 身份驗證(使用Google 登錄或電話號碼登錄時)和Firebase 動態鏈接需要SHA-1 哈希。
單擊註冊應用程序。
添加 Firebase 配置文件
下載然後將 Firebase Android 配置文件 (
) 添加到您的應用程序:google-services.json 單擊下載 google-services.json以獲取您的 Firebase Android 配置文件。
將您的配置文件移動到應用程序的模塊(應用程序級)根目錄中。
Firebase 配置文件包含項目的唯一但非機密標識符。要了解有關此配置文件的更多信息,請訪問了解 Firebase 項目。
您可以隨時再次下載您的Firebase 配置文件。
確保配置文件名未附加其他字符,例如
(2)
。
要使
配置文件中的值可供 Firebase SDK 訪問,您需要Google 服務 Gradle 插件(google-services.json google-services
)。在您的根級(項目級) Gradle 文件 (
<project>/build.gradle
) 中,將 Google 服務插件添加為構建腳本依賴項:buildscript { repositories { // Make sure that you have the following two repositories google() // Google's Maven repository mavenCentral() // Maven Central repository } dependencies { ... // Add the dependency for the Google services Gradle plugin classpath 'com.google.gms:google-services:4.3.15' } } allprojects { ... repositories { // Make sure that you have the following two repositories google() // Google's Maven repository mavenCentral() // Maven Central repository } }
在您的模塊(應用程序級別) Gradle 文件(通常是
<project>/<app-module>/build.gradle
)中,添加 Google 服務插件:plugins { id 'com.android.application' // Add the Google services Gradle plugin id 'com.google.gms.google-services' ... }
將 Firebase SDK 添加到您的應用
在您的模塊(應用級)Gradle 文件(通常為
<project>/<app-module>/build.gradle
)中,添加 Firebase Cloud Messaging Android 庫的依賴項。我們建議使用Firebase Android BoM來控制庫版本。為了獲得 Firebase 雲消息傳遞的最佳體驗,我們建議在您的 Firebase 項目中啟用 Google Analytics ,並將適用於 Google Analytics 的 Firebase SDK 添加到您的應用程序中。
Java
dependencies { // Import the BoM for the Firebase platform implementation platform('com.google.firebase:firebase-bom:32.1.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 庫。
(備選)在不使用 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.1.2' implementation 'com.google.firebase:firebase-analytics:21.3.0' }
Kotlin+KTX
dependencies { // Import the BoM for the Firebase platform implementation platform('com.google.firebase:firebase-bom:32.1.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-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' }
通過使用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-ktx:23.1.2' implementation 'com.google.firebase:firebase-analytics-ktx:21.3.0' }
將您的 Android 項目與 Gradle 文件同步。
使用 Android Gradle 插件 (AGP) v4.2 或更早版本的 Gradle 構建需要啟用 Java 8 支持。否則,這些 Android 項目在添加 Firebase SDK 時會出現構建失敗。
要修復此構建失敗,您可以遵循以下兩個選項之一:
- 將錯誤消息中列出的
compileOptions
添加到您的應用程序級build.gradle
文件中。 - 將 Android 項目的
minSdkVersion
增加到 26 或更高。
在此常見問題解答中了解有關此構建失敗的更多信息。
- 將錯誤消息中列出的
為客戶端應用程序訂閱主題
客戶端應用程序可以訂閱任何現有主題,也可以創建新主題。當客戶端應用程序訂閱一個新主題名稱(您的 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服務。您的服務應該覆蓋onMessageReceived
和onDeletedMessages
回調。它應該在收到消息後的 20 秒內處理任何消息(在 Android Marshmallow 上為 10 秒)。時間窗口可能會更短,具體取決於調用onMessageReceived
之前發生的操作系統延遲。在那之後,各種操作系統行為(例如 Android O 的後台執行限制)可能會干擾您完成工作的能力。有關詳細信息,請參閱我們對消息優先級的概述。
onMessageReceived
為大多數消息類型提供,但以下情況除外:
當您的應用程序處於後台時發送的通知消息。在這種情況下,通知將傳送到設備的系統托盤。默認情況下,用戶點擊通知會打開應用程序啟動器。
在後台接收時同時具有通知和數據負載的消息。在這種情況下,通知會傳送到設備的系統托盤,數據負載會在啟動器 Activity 的 intent 的附加項中傳送。
總之:
應用狀態 | 通知 | 數據 | 兩個都 |
---|---|---|---|
前景 | 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 顯示自定義默認圖標
- 從Notifications composer發送的所有通知消息。
- 任何未在通知負載中明確設置圖標的通知消息。
Android 使用自定義默認顏色
- 從Notifications composer發送的所有通知消息。
- 任何未在通知負載中明確設置顏色的通知消息。
如果未設置自定義默認圖標並且在通知有效負載中也未設置圖標,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),或者如果該設備超過一個月未連接到 FCM,就會發生這種情況。在這些情況下,您可能會收到FirebaseMessagingService.onDeletedMessages()
的回調當應用實例收到此回調時,它應該與您的應用服務器執行完全同步。如果您在過去 4 週內沒有向該設備上的應用發送消息,FCM 將不會調用onDeletedMessages()
。在後台應用程序中處理通知消息
當您的應用程序處於後台時,Android 會將通知消息定向到系統托盤。默認情況下,用戶點擊通知會打開應用程序啟動器。
這包括包含通知和數據負載的消息(以及從通知控制台發送的所有消息)。在這些情況下,通知會傳送到設備的系統托盤,並且數據負載會在啟動器 Activity 的 intent 的附加項中傳送。
要深入了解向您的應用程序發送的消息,請參閱FCM 報告儀表板,它記錄了在 Apple 和 Android 設備上發送和打開的消息數量,以及 Android 應用程序的“印象”(用戶看到的通知)數據。
構建發送請求
創建主題後,通過在客戶端訂閱客戶端應用程序實例或通過服務器 API訂閱主題,您可以向主題發送消息。如果這是您第一次為 FCM 構建發送請求,請參閱您的服務器環境和 FCM指南,了解重要的背景和設置信息。
在後端的發送邏輯中,指定所需的主題名稱,如下所示:
節點.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
要向主題組合發送消息,請指定condition ,它是指定目標主題的布爾表達式。例如,以下條件會將消息發送到訂閱了TopicA
和TopicB
或TopicC
的設備:
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM 首先計算括號中的任何條件,然後從左到右計算表達式。在上面的表達式中,訂閱任何單個主題的用戶不會收到消息。同樣,未訂閱TopicA
用戶也不會收到消息。這些組合確實收到了它:
-
TopicA
和TopicB
-
TopicA
和TopicC
您最多可以在條件表達式中包含五個主題。
發送到一個條件:
節點.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
下一步
- 您可以使用您的服務器為客戶端應用程序實例訂閱主題並執行其他管理任務。請參閱管理服務器上的主題訂閱。
- 了解更多關於發送到多個設備的另一種方式——設備組消息