向多台设备发送消息

Firebase 云消息传递提供两种向多台设备发送消息的方法:

本教程主要介绍如何使用 FCM 的 HTTP 协议或 XMPP 协议从应用服务器发送主题消息,以及如何在 Android 应用中接收和处理此类消息。介绍内容涵盖后台应用和前台应用的消息处理方式。此外,我们还介绍了实现上述目标所需的所有步骤,包括从设置到验证的步骤。

设置 SDK

如果您已针对 FCM 设置 Android 客户端应用,或已经完成发送第一条消息所需的步骤,则本节可能包含了您已经完成的步骤。

前提条件

  • 一台运行以下系统及应用的设备:
    • Android 4.1(API 级别 14,Jelly Bean)或更高版本
    • Google Play 服务 15.0.0 或更高版本
  • 最新版本的 Android Studio

如果您还没有 Android Studio 项目,只是想试用一下某项 Firebase 功能,可以下载我们的快速入门示例。如果使用快速入门,请不要忘记从项目模块文件夹(通常是 build.gradle)的 app/ 文件中获取应用 ID;您需要在下一步中使用该软件包名称。

将 Firebase 添加到您的应用

现在需要将 Firebase 添加至您的应用。要执行此操作,您需要有一个 Firebase 项目和适用于您的应用的 Firebase 配置文件。

要创建 Firebase 项目,请执行以下操作:

  1. 访问 Firebase 控制台

  2. 点击添加项目,然后选择或输入项目名称

    • 如果您已经拥有一个与应用关联的 Google 项目,请从项目名称下拉菜单中选择该项目。
    • 如果您尚未创建 Google 项目,请输入新的项目名称
  3. (可选)修改项目 ID

    Firebase 会自动为您的 Firebase 项目分配一个独一无二的 ID。此 ID 会显示在公众可见的 Firebase 服务中,例如:

    • 默认实时数据库网址:your-project-id.firebaseio.com
    • 默认 Cloud Storage 存储分区名称:your-project-id.appspot.com
    • 默认托管子网域:your-project-id.firebaseapp.com
  4. 在 Firebase 控制台中完成剩下的设置步骤,然后点击创建项目(如果您使用的是现有 Google 项目,则点击添加 Firebase)。

Firebase 会自动为您的 Firebase 项目预配资源。此过程通常需要几分钟。完成此过程后,您将进入 Firebase 控制台中 Firebase 项目的概览页面。

创建项目之后,您就可以向其中添加 Android 应用了:

  1. 点击将 Firebase 添加到您的 Android 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。

  2. 出现提示时,输入应用的软件包名称。请务必输入应用使用的软件包名称;只有在将应用添加到 Firebase 项目时您才能进行此设置。

  3. 将 Firebase Android 配置文件添加到您的应用:

    1. 点击下载 google-services.json 以获取 Firebase Android 配置文件 (google-services.json)。

      您可以随时再次下载 Firebase Android 配置文件

    2. 将配置文件移至与根级 build.gradle 文件相同的目录中。

  4. 添加初始化代码后,运行您的应用以便向 Firebase 控制台发送验证信息,证明您已成功安装 Firebase。

添加 SDK

如果希望将 Firebase 库集成至自己的某个项目中,您需要执行几项基本操作来准备 Android Studio 项目。您可能已经在将 Firebase 添加至应用时完成了这些操作。

首先,向您的根级 build.gradle 文件添加规则,以纳入 google-services 插件和 Google 的 Maven 代码库:

buildscript {
    // ...
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.2.0' // google-services plugin
    }
}

allprojects {
    // ...
    repositories {
        // ...
        google() // Google's Maven repository
    }
}

然后,在您的模块 Gradle 文件(通常是 app/build.gradle)中,在文件的底部添加 apply plugin 代码行,以启用 Gradle 插件:

apply plugin: 'com.android.application'

android {
  // ...
}

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-core:16.0.6'
  implementation 'com.google.firebase:firebase-messaging:17.3.4'
  // Getting a "Could not find" error? Make sure you have
  // added the Google maven respository to your root build.gradle
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

您还应添加您希望使用的 Firebase SDK 的依赖项。建议从 com.google.firebase:firebase-core 开始,它可以提供 Google Analytics for Firebase 功能。请参阅可用库列表

为客户端应用订阅主题

客户端应用可以订阅任何现有主题,也可创建新主题。当客户端应用订阅新的主题名称(您的 Firebase 项目中尚不存在的名称)时,系统会在 FCM 中使用这个名称创建一个新主题,随后任何客户端都可订阅该主题。

若要订阅某个主题,客户端应用需使用 FCM 主题名称调用 Firebase 云消息传递的 subscribeToTopic() 方法。此方法会返回一个 Task,完成侦听器可以使用它来确定订阅是否成功:

Java
Android

FirebaseMessaging.getInstance().subscribeToTopic("weather")
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                String msg = getString(R.string.msg_subscribed);
                if (!task.isSuccessful()) {
                    msg = getString(R.string.msg_subscribe_failed);
                }
                Log.d(TAG, msg);
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
            }
        });

Kotlin
Android

FirebaseMessaging.getInstance().subscribeToTopic("weather")
        .addOnCompleteListener { task ->
            var msg = getString(R.string.msg_subscribed)
            if (!task.isSuccessful) {
                msg = getString(R.string.msg_subscribe_failed)
            }
            Log.d(TAG, msg)
            Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        }

若要退订,客户端应用需使用主题名称调用 Firebase 云消息传递的 unsubscribeFromTopic() 方法。

接收和处理主题消息

FCM 传送主题消息的方式与处理其他下行消息的方式相同。

要接收消息,请使用可以扩展 FirebaseMessagingService 的服务。您的服务应该覆盖 onMessageReceivedonDeletedMessages 回调。在收到任何消息后,该服务都应在 20 秒内处理消息(Android Marshmallow 上为 10 秒)。根据调用 onMessageReceived 之前发生的操作系统延迟,时间窗口可能会更短。在此之后,各种操作系统行为(如 Android O 的后台执行限制)可能会影响您完成工作的能力。如需了解详情,请参阅我们的消息优先级概述。

大多数类型的消息都会提供 onMessageReceived,但以下情况例外:

  • 当应用在后台时送达的通知消息。在这种情况下,通知将传送至设备的系统任务栏。默认情况下,用户点按通知即可打开应用启动器。

  • 当应用在后台时同时具备通知和数据负载的消息。在这种情况下,通知将传送至设备的系统任务栏,数据有效负载则传送至启动器 Activity 的 intent 的 extras 参数中。

汇总:

应用状态 通知 数据 两者
前台 onMessageReceived onMessageReceived onMessageReceived
后台 系统任务栏 onMessageReceived 通知:系统任务栏
数据:intent 的 extras 参数。
如需了解消息类型的详细信息,请参阅通知和数据消息

修改应用清单文件

要使用 FirebaseMessagingService,您需要将以下内容添加到您的应用清单:

<service android:name=".java.MyFirebaseMessagingService">
    <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 对象执行操作并获取消息数据:

Java
Android

@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 Firebase Job Dispatcher.
            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.
}

Kotlin
Android

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.
    remoteMessage?.data?.isNotEmpty()?.let {
        Log.d(TAG, "Message data payload: " + remoteMessage.data)

        if (/* Check if data needs to be processed by long running job */ true) {
            // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
            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.
}

重写 onDeletedMessages

在某些情况下,FCM 可能不会传递消息。如果在特定设备连接 FCM 时,您的应用在该设备上的待处理消息过多(超过 100 条),或者如果设备超过一个月未连接到 FCM,就会发生这种情况。在这些情况下,您可能会收到对 FirebaseMessagingService.onDeletedMessages() 的回调。当应用实例收到此回调时,应会执行一次与您的应用服务器的完全同步。如果您在过去 4 周内未向该设备上的应用发送消息,FCM 将不会调用 onDeletedMessages()

在后台应用中处理通知消息

当您的应用位于后台时,Android 会将通知消息转发至系统任务栏。默认情况下,用户点按通知时将打开应用启动器。

这包括同时含有通知和数据有效负载的消息(以及从通知控制台发送的所有消息)。在这些情况下,通知将传送至设备的系统任务栏,数据有效负载则传送至启动器 Activity 的 intent 的 extras 参数中。

有关发送到您应用的消息的数据分析,请参阅 FCM 报告信息中心,该信息中心记录在 iOS 和 Android 设备上发送和打开的消息数量,以及 Android 应用的“展示次数”(用户看到的通知)数据。

后台受限应用(Android P 或更高版本)

从 2019 年 1 月开始,FCM 将不会向由用户置于后台限制的应用传送消息(例如,通过“设置”->“应用和通知”->“[appname]”->“电池”)。一旦您的应用解除了后台限制,系统就会像以前一样向该应用传送新消息。为了防止消息丢失以及其他后台限制影响,一定要避免 Android Vitals 计划所列的不良行为。这些行为可能会使 Android 设备向用户提供将您的应用置于后台限制的建议。您的应用可以使用 isBackgroundRestricted() 检查其是否受到后台限制。

构建发送请求

向 Firebase 云消息传递主题发送消息与向单台设备或一个用户组发送消息十分类似。应用服务器会将 to 键设为类似 /topics/yourTopic 这样的值。开发者可以选择符合以下正则表达式的任何主题名称:"/topics/[a-zA-Z0-9-_.~%]+"

要向多个主题的组合发送消息,应用服务器必须将 condition 键(而非 to 键)设为用于指定目标主题的布尔型条件。例如,要向已订阅 TopicATopicBTopicC 的设备发送消息,则设置如下条件:

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

FCM 首先对括号中的所有条件求值,然后从左至右对表达式求值。在上述表达式中,只订阅某个单一主题的用户将不会接收到消息。同样地,未订阅 TopicA 的用户也不会接收到消息。下列组合将会接收到消息:

  • TopicA 和 TopicB
  • TopicA 和 TopicC

您最多可以在条件表达式中添加五个主题,支持使用括号。支持的运算符包括 &&||!。请注意 ! 的用法:

!('TopicA' in topics)

使用此表达式,任何未订阅 TopicA 的应用实例(包括未订阅任何主题的应用实例)都会收到消息。

如需详细了解应用服务器密钥,请参阅所选的连接服务器协议(HTTPXMPP)的参考信息。本页面中的示例说明了如何使用 HTTP 和 XMPP 协议将消息发送至主题。

主题 HTTP POST 请求

发送至一个主题:

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "to": "/topics/foo-bar",
  "data": {
    "message": "This is a Firebase Cloud Messaging Topic Message!",
   }
}

发送至已订阅“dogs”或“cats”主题的设备:

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "condition": "'dogs' in topics || 'cats' in topics",
  "data": {
    "message": "This is a Firebase Cloud Messaging Topic Message!",
   }
}

主题 HTTP 响应

//Success example:
{
  "message_id": "1023456"
}

//failure example:
{
  "error": "TopicsMessageRateExceeded"
}

主题 XMPP 消息

发送至一个主题:

<message id="">
  <gcm xmlns="google:mobile:data">
{
  "to": "/topics/foo-bar",
  "data": {
    "message": "This is a Firebase Cloud Messaging Topic Message!",
   }
}

  </gcm>
</message>

发送至已订阅“dogs”或“cats”主题的设备:

<message id="">
  <gcm xmlns="google:mobile:data">
{
  "condition": "'dogs' in topics || 'cats' in topics",
  "data": {
    "message": "This is a Firebase Cloud Messaging Topic Message!",
   }
}

  </gcm>
</message>

主题 XMPP 响应

//Success example:
{
  "message_id": "1023456"
}

//failure example:
{
  "error": "TopicsMessageRateExceeded"
}

FCM 服务器对主题发送请求返回成功或失败响应前,可能会有最长 30 秒的延迟。请务必在请求中相应地设置应用服务器的超时值。

如需消息选项的完整列表,请参阅您所选的连接服务器协议(HTTPXMPP)的参考信息。

后续步骤

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面