获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Android 上的主题消息传递

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

基于发布/订阅模型,FCM 主题消息传递允许您将消息发送到已选择加入特定主题的多个设备。您可以根据需要编写主题消息,FCM 会处理路由并将消息可靠地传递到正确的设备。

例如,当地潮汐预报应用程序的用户可以选择加入“潮汐警报”主题并接收指定区域最佳咸水捕鱼条件的通知。体育应用程序的用户可以订阅他们最喜欢的球队的实时比赛比分的自动更新。

关于主题需要记住的一些事项:

  • 主题消息最适合天气等内容或其他公开可用的信息。
  • 主题消息针对吞吐量而不是延迟进行了优化。为了快速、安全地传递到单个设备或小组设备,请将消息定位到注册令牌,而不是主题。
  • 如果您需要将消息发送到每个用户的多个设备,请考虑针对这些用例进行设备群组消息传递
  • 主题消息支持每个主题的无限订阅。但是,FCM 在以下领域实施限制:
    • 一个应用实例最多可以订阅 2000 个主题。
    • 如果您使用批量导入订阅应用实例,则每个请求限制为 1000 个应用实例。
    • 新订阅的频率受每个项目的速率限制。如果您在短时间内发送太多订阅请求,FCM 服务器将响应429 RESOURCE_EXHAUSTED (“超出配额”)响应。使用指数退避重试。

为客户端应用订阅主题

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

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

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

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

要取消订阅,客户端应用程序使用主题名称调用 Firebase Cloud Messaging unsubscribeFromTopic()

管理服务器上的主题订阅

Firebase Admin SDK允许您从服务器端执行基本的主题管理任务。鉴于他们的注册令牌,您可以使用服务器逻辑批量订阅和取消订阅客户端应用程序实例。

您可以将客户端应用程序实例订阅到任何现有主题,也可以创建新主题。当您使用 API 为客户端应用订阅新主题(您的 Firebase 项目尚不存在的主题)时,会在 FCM 中创建一个具有该名称的新主题,随后任何客户端都可以订阅该主题。

您可以将注册令牌列表传递给 Firebase Admin SDK 订阅方法,以便为相应的设备订阅主题:

节点.js

// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
  'YOUR_REGISTRATION_TOKEN_1',
  // ...
  'YOUR_REGISTRATION_TOKEN_n'
];

// Subscribe the devices corresponding to the registration tokens to the
// topic.
getMessaging().subscribeToTopic(registrationTokens, topic)
  .then((response) => {
    // See the MessagingTopicManagementResponse reference documentation
    // for the contents of response.
    console.log('Successfully subscribed to topic:', response);
  })
  .catch((error) => {
    console.log('Error subscribing to topic:', error);
  });

爪哇

// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n"
);

// Subscribe the devices corresponding to the registration tokens to the
// topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().subscribeToTopic(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were subscribed successfully");

Python

# These registration tokens come from the client FCM SDKs.
registration_tokens = [
    'YOUR_REGISTRATION_TOKEN_1',
    # ...
    'YOUR_REGISTRATION_TOKEN_n',
]

# Subscribe the devices corresponding to the registration tokens to the
# topic.
response = messaging.subscribe_to_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were subscribed successfully')

// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
	"YOUR_REGISTRATION_TOKEN_1",
	// ...
	"YOUR_REGISTRATION_TOKEN_n",
}

// Subscribe the devices corresponding to the registration tokens to the
// topic.
response, err := client.SubscribeToTopic(ctx, registrationTokens, topic)
if err != nil {
	log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were subscribed successfully")

C#

// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n",
};

// Subscribe the devices corresponding to the registration tokens to the
// topic
var response = await FirebaseMessaging.DefaultInstance.SubscribeToTopicAsync(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were subscribed successfully");

Admin FCM API 还允许您通过将注册令牌传递给适当的方法来取消订阅设备的主题:

节点.js

// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
  'YOUR_REGISTRATION_TOKEN_1',
  // ...
  'YOUR_REGISTRATION_TOKEN_n'
];

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
getMessaging().unsubscribeFromTopic(registrationTokens, topic)
  .then((response) => {
    // See the MessagingTopicManagementResponse reference documentation
    // for the contents of response.
    console.log('Successfully unsubscribed from topic:', response);
  })
  .catch((error) => {
    console.log('Error unsubscribing from topic:', error);
  });

爪哇

// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n"
);

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().unsubscribeFromTopic(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were unsubscribed successfully");

Python

# These registration tokens come from the client FCM SDKs.
registration_tokens = [
    'YOUR_REGISTRATION_TOKEN_1',
    # ...
    'YOUR_REGISTRATION_TOKEN_n',
]

# Unubscribe the devices corresponding to the registration tokens from the
# topic.
response = messaging.unsubscribe_from_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were unsubscribed successfully')

// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
	"YOUR_REGISTRATION_TOKEN_1",
	// ...
	"YOUR_REGISTRATION_TOKEN_n",
}

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
response, err := client.UnsubscribeFromTopic(ctx, registrationTokens, topic)
if err != nil {
	log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were unsubscribed successfully")

C#

// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n",
};

// Unsubscribe the devices corresponding to the registration tokens from the
// topic
var response = await FirebaseMessaging.DefaultInstance.UnsubscribeFromTopicAsync(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were unsubscribed successfully");

subscribeToTopic()unsubscribeFromTopic()方法生成一个包含来自 FCM 的响应的对象。无论请求中指定的注册令牌数量如何,返回类型都具有相同的格式。

如果出现错误(身份验证失败、无效令牌或主题等),这些方法会导致错误。有关错误代码的完整列表,包括说明和解决步骤,请参阅Admin FCM API Errors

接收和处理主题消息

FCM 以与其他下游消息相同的方式传递主题消息。

要接收消息,请使用扩展FirebaseMessagingService的服务。您的服务应覆盖onMessageReceivedonDeletedMessages回调。它应该在收到后 20 秒内处理任何消息(Android Marshmallow 为 10 秒)。时间窗口可能会更短,具体取决于调用onMessageReceived之前发生的操作系统延迟。在那之后,各种操作系统行为(例如 Android O 的后台执行限制)可能会干扰您完成工作的能力。有关更多信息,请参阅我们的消息优先级概述。

大多数消息类型都提供了onMessageReceived ,但以下情况除外:

  • 当您的应用程序在后台时传递的通知消息。在这种情况下,通知将传送到设备的系统托盘。默认情况下,用户点击通知会打开应用启动器。

  • 在后台接收时带有通知和数据负载的消息。在这种情况下,通知被传递到设备的系统托盘,数据负载被传递到启动器 Activity 的附加内容中。

总之:

应用状态通知数据两个都
前景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对象执行操作并获取消息数据:

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

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

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

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

当您的应用程序处于后台时,Android 会将通知消息定向到系统托盘。默认情况下,用户点击通知会打开应用启动器。

这包括同时包含通知和数据负载的消息(以及从通知控制台发送的所有消息)。在这些情况下,通知会被传递到设备的系统托盘,而数据负载会在启动器 Activity 的意图的附加部分中传递。

要深入了解向您的应用程序传递的消息,请参阅FCM 报告仪表板,它记录了在 Apple 和 Android 设备上发送和打开的消息数量,以及 Android 应用程序的“展示次数”(用户看到的通知)的数据。

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

FCM 可能不会向用户设置后台限制的应用程序发送消息(例如通过:设置 -> 应用程序和通知 -> [应用程序名称] -> 电池)。一旦您的应用程序从后台限制中删除,新消息将像以前一样传递到该应用程序。为了防止丢失消息和其他后台限制影响,请确保避免Android Vitals努力列出的不良行为。这些行为可能会导致 Android 设备向用户建议您的应用受后台限制。你的应用可以使用isBackgroundRestricted()检查它是否被后台限制。

构建发送请求

创建主题后,通过在客户端订阅客户端应用程序实例或通过服务器 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

要将消息发送到主题组合,请指定条件,它是指定目标主题的布尔表达式。例如,以下条件将向订阅了TopicATopicBTopicC的设备发送消息:

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

FCM 首先计算括号中的任何条件,然后从左到右计算表达式。在上面的表达式中,订阅任何单个主题的用户都不会收到消息。同样,未订阅TopicA的用户也不会收到该消息。这些组合确实收到了它:

  • TopicATopicB
  • TopicATopicC

您最多可以在条件表达式中包含五个主题。

发送到条件:

节点.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

下一步