转到控制台

向多台设备发送消息

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

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

设置 SDK

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

准备工作

  • 安装 Android Studio 或将其更新为最新版本。

  • 确保您的 Android 应用符合以下条件:

    • 目标为 API 级别 16 (Jelly Bean) 或更高版本
    • 使用 Gradle 4.1 或更高版本
  • 设置可用于运行应用的设备或模拟器。

    • 模拟器必须使用具有 Google Play 的模拟器映像。
  • 使用您的 Google 帐号登录 Firebase

如果您还没有 Android 应用项目,只是想试用一下某个 Firebase 产品,可以下载我们的快速入门示例

创建 Firebase 项目

您必须先创建一个 Firebase 项目,并将其关联到您的 Android 应用,然后才能将 Firebase 添加至您的 Android 应用。访问了解 Firebase 项目以详细了解 Firebase 项目。

使用 Firebase 注册您的应用

拥有 Firebase 项目后,您就可以向其中添加 Android 应用了。

访问了解 Firebase 项目以详细了解将应用添加到 Firebase 项目的最佳做法和注意事项,包括如何处理多个应用版本。

  1. Firebase 控制台的项目概览页面的中心位置,点击 Android 图标以启动设置工作流。

    如果您已向 Firebase 项目添加了应用,请点击添加应用以显示平台选项。

  2. Android 软件包名称字段中输入您的应用 ID

    • “应用 ID”有时被称为“软件包名称”

    • 在您的模块(应用级)Gradle 文件(通常是 app/build.gradle)中找到此应用 ID(如 ID:com.yourcompany.yourproject)。

  3. (可选)根据设置工作流的提示输入其他应用信息。

    昵称是方便内部使用的标识符,只有您能在 Firebase 控制台中看到。

  4. 点击注册应用

添加 Firebase 配置文件

  1. 将 Firebase Android 配置文件添加至您的应用:

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

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

    2. 接着将配置文件移动到应用的模块(应用级)目录中。

  2. 要在 Android 应用中启用 Firebase 产品,请将 Google 服务插件添加到 Gradle 文件中。

    1. 在根级(项目级)Gradle 文件 (build.gradle) 中添加相应规则,以包含 Google 服务插件。此外,请确认您是否拥有 Google 的 Maven 代码库。

      buildscript {
        // ...
        dependencies {
          // ...
          // Add the following line:
          classpath 'com.google.gms:google-services:4.2.0'  // Google Services plugin
        }
      }
      
      allprojects {
        // ...
        repositories {
          // Check that you have the following line (if not, add it):
          google()  // Google's Maven repository
          // ...
        }
      }
      
    2. 在您的模块(应用级)Gradle 文件(通常是 app/build.gradle)中,将下面一行添加到文件末尾

      apply plugin: 'com.android.application'
      
      android {
        // ...
      }
      
      // Add the following line to the bottom of the file:
      apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin
      

将 Firebase SDK 添加至您的应用

您可以将任何受支持的 Firebase 产品添加至 Android 应用中。我们建议您先从添加核心 Firebase SDK (com.google.firebase:firebase-core) 开始,该产品提供有 Google Analytics for Firebase 功能。

  1. 在您的模块(应用级)Gradle 文件(通常是 app/build.gradle)中,添加核心 Firebase SDK 的依赖项:

    dependencies {
     // ...
     implementation 'com.google.firebase:firebase-core:16.0.8'
    
     // Getting a "Could not find" error? Make sure that you've added
     // Google's Maven repository to your root-level build.gradle file
    }
    
  2. (可选)添加要使用的其他 Firebase 库的依赖项。

    一些适用于 Android 的 Firebase SDK 提供了备选 Kotlin 扩展程序库

  3. 同步您的应用以确保所有依赖项都具有必要的版本。

  4. 运行您的应用,向 Firebase 发送您已成功集成 Firebase 的验证信息。

    您的设备日志将显示说明初始化已完成的 Firebase 验证信息。如果您在具有网络访问权限的模拟器上运行应用,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"
    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 会显示自定义默认图标:

  • 通知编辑器发送的所有通知信息。
  • 在通知负载中未明确设置图标的任何通知消息。

对于以下内容,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 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
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 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 可能不会传递消息。如果在特定设备连接 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() 检查其是否受到后台限制。

构建发送请求

您创建一个主题后,可以通过两种方法向主题发送消息:一是在客户端为客户端应用实例订阅主题,二是通过服务器 API。在后端的发送逻辑中,按如下所示指定所需的主题名称:

Node.js

// The topic name can be optionally prefixed with "/topics/".
var topic = 'highScores';

var message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
admin.messaging().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 以及 TopicBTopicC 的设备:

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

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

  • TopicATopicB
  • TopicATopicC

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

向条件发送消息:

Node.js

// 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 = {
  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.
admin.messaging().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(new Notification(
        "$GOOG up 1.43% on the day",
        "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day."))
    .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

后续步骤