Firebase Cloud Messaging 提供两种向多台设备发送消息的方法:
本教程主要介绍如何使用适用于 FCM 的 Admin SDK 或 REST API 从应用服务器发送主题消息,并在 Android 应用中接收和处理此类消息。内容涵盖后台应用和前台应用的消息处理方式。我们将介绍实现上述目标所需执行的步骤,包括从设置到验证的所有步骤。
设置 SDK
如果您已针对 FCM 设置 Android 客户端应用,或已经完成发送第一条消息所需执行的步骤,则本部分可能包含了您已经完成的步骤。
准备工作
安装最新版本的 Android Studio,或将其更新为最新版本。
确保您的项目满足以下要求:
- 目标 API 级别为 19 (KitKat) 或更高
- 使用 Android 4.4 或更高版本
- 使用 Jetpack (AndroidX),这需要满足以下版本要求:
com.android.tools.build:gradle
3.2.1 或更高版本compileSdkVersion
28 或更高版本
设置一台实体设备或使用模拟器运行您的应用。
请注意,依赖于 Google Play 服务的 Firebase SDK 要求设备或模拟器上必须安装 Google Play 服务。使用您的 Google 帐号登录 Firebase。
如果您还没有 Android 项目,只是想试用某一 Firebase 产品,可以下载一个我们的快速入门示例。
创建 Firebase 项目
您必须先创建一个要关联到 Android 应用的 Firebase 项目,然后才能将 Firebase 添加到您的 Android 应用。如需详细了解 Firebase 项目,请访问了解 Firebase 项目。
在 Firebase 中注册您的应用
如需在 Android 应用中使用 Firebase,您需要在 Firebase 项目中注册您的应用。注册应用的过程通常称为将应用“添加”到项目中。
前往 Firebase 控制台。
在项目概览页面的中心位置,点击 Android 图标 (
) 或添加应用,启动设置工作流。在 Android 软件包名称字段中输入应用的软件包名称。
(可选)输入其他应用信息:应用别名和调试签名证书 SHA-1。
点击注册应用。
添加 Firebase 配置文件
下载 Firebase Android 配置文件 (
),然后将其添加到您的应用:google-services.json 点击下载 google-services.json 以获取 Firebase Android 配置文件。
将配置文件移到应用的模块(应用级)根目录中。
为了确保 Firebase SDK 可以访问
配置文件中的值,您需要具有 Google 服务 Gradle 插件 (google-services.json google-services
)。在您的根级(项目级)Gradle 文件 (
<project>/build.gradle
) 中,将 Google 服务插件添加为 buildscript 依赖项: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 Cloud Messaging 使用体验,我们建议您在 Firebase 项目中启用 Google Analytics(分析),并将 Firebase SDK for Google Analytics 添加到您的应用中。
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 文件同步。
为客户端应用订阅主题
客户端应用可以订阅任何现有主题,也可以创建新主题。当客户端应用订阅新的主题名称(您的 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 的 extras 属性中。
总结:
应用状态 | 通知 | 数据 | 两者皆有 |
---|---|---|---|
前台 | onMessageReceived |
onMessageReceived |
onMessageReceived |
后台 | 系统任务栏 | onMessageReceived |
通知:系统任务栏 数据:intent 的 extras 属性。 |
修改应用清单
如需使用 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 可能不会传递消息。比如说,如果在特定设备连接到 FCM 时,您的应用在该设备上的待处理消息过多(超过 100 条),或者设备超过一个月未连接到 FCM,就会发生这种情况。在这些情况下,您可能会收到对 FirebaseMessagingService.onDeletedMessages()
的回调。当应用实例收到此回调时,应该执行一次与应用服务器的完全同步。如果您在过去 4 周内未向该设备上的应用发送消息,FCM 将不会调用 onDeletedMessages()
。在后台应用中处理通知消息
当您的应用在后台运行时,Android 会将通知消息传送至系统任务栏。默认情况下,用户点按通知即可打开应用启动器。
此类消息包括既具有通知也具有数据载荷的消息(以及所有从 Notifications 控制台发送的消息)。 在这些情况下,通知将传送至设备的系统任务栏,数据载荷则传送至启动器 Activity 的 intent 的 extras 属性。
如需详细了解发送到您应用的消息,请参阅 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);
});
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
如需向主题组合发送消息,请指定一个条件,该条件是一个指定目标主题的布尔表达式。例如,如需向既订阅了 TopicA
,又订阅了 TopicB
或 TopicC
的设备发送消息,请设置如下条件:
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM 首先对括号中的所有条件求值,然后从左至右对表达式求值。在上述表达式中,只订阅了其中某个主题的用户将不会接收到消息。同样地,未订阅 TopicA
的用户也不会接收到消息。订阅下列组合的用户才会接收到消息:
TopicA
和TopicB
TopicA
和TopicC
您最多可以在条件表达式中包含五个主题。
根据条件发送消息:
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
后续步骤
- 您可以使用服务器为客户端应用实例订阅主题并执行其他管理任务。请参阅在服务器上管理主题订阅。
- 详细了解向多台设备发送消息的另一种方式 - 设备组消息传递