Android 上的主题消息传递

基于其发布/订阅的模式,FCM 主题消息传递功能可让您将消息发送至已经选择加入特定主题的多台设备。您根据需要撰写主题消息,FCM 负责确定发布路径并将消息可靠地传送至正确的设备。

例如,某个本地潮汐预报应用的用户可选择加入“潮流警报”主题,并接收关于指定区域最佳海水捕捞条件的通知。体育应用的用户可以订阅他们心仪球队的实时比分自动更新。

关于主题,请注意以下事项:

  • 主题消息传递最适合传递天气或其他可通过公开途径获得的信息。
  • 主题消息的优化重心是吞吐量而非延迟。要将消息快速安全地传送到单台设备或小规模设备组,应将消息定位至注册令牌,而非主题。
  • 如果您需要向一位用户的多台设备发送消息,可考虑在这些使用场景中使用设备组消息传递
  • 主题消息传递不限制每个主题的订阅数。但是,FCM 在这些方面有强制限制:
    • 一个应用实例不可订阅超过 2000 个主题。
    • 如果您正在使用批量导入来订阅应用实例,则每次请求仅限订阅 1000 个应用实例。
    • 每个项目新订阅频率的速率受限。如果您在短时间内发送过多订阅请求,FCM 服务器将给出 429 RESOURCE_EXHAUSTED(“已超出配额”)响应。使用指数退避算法重试。

为客户端应用订阅主题

客户端应用可以订阅任何现有主题,也可创建新主题。当客户端应用订阅新的主题名称(您的 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() 方法。

在服务器上管理主题订阅

您可以利用 Instance ID API 从服务器端执行基本主题管理任务。如果有客户端应用实例的注册令牌,您可以执行下列操作:

接收和处理主题消息

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

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

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

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

  • TopicA 和 TopicB
  • TopicA 和 TopicC

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

!('TopicA' in topics)

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

如需详细了解应用服务器键,请参阅参考信息

主题 HTTP POST 请求

发送至单个主题:

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

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

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

主题 HTTP 响应

{
    "name": "projects/myproject-b5ae1/messages/5735743068807585451"
}

如需消息选项的完整列表,请参阅 HTTP v1 API 参考

后续步骤

发送以下问题的反馈:

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