转到控制台

向多台设备发送消息

Firebase 云消息传递提供了以下两种将消息发送至多台设备的方法:

本教程主要介绍如何使用 FCM 的 HTTP 协议或 XMPP 协议从应用服务器发送主题消息,以及如何在网页应用中接收和处理此类消息,内容涵盖后台应用和前台应用的消息处理方式。

设置 SDK

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

将 Firebase 添加到您的 JavaScript 项目

如果尚未将 Firebase 添加到您的 JavaScript 项目,请进行添加。

配置浏览器以接收消息

您需要添加一个网页应用清单,并在其中指定 gcm_sender_id(一个硬编码值,表示 FCM 有权向此应用发送消息)。如果您的应用已经有一个 manifest.json 配置文件,请务必完全如下所示添加浏览器发送者 ID(不要更改这个值):

{
  "gcm_sender_id": "103953800507"
}

检索消息对象

// Retrieve Firebase Messaging object.
const messaging = firebase.messaging();

申请接收通知的权限

messaging.requestPermission() 方法可显示一个征询用户同意的对话框,让用户在浏览器中向您的应用授予接收通知的权限。如果权限被拒绝,FCM 注册令牌请求会导致错误。

messaging.requestPermission().then(function() {
  console.log('Notification permission granted.');
  // TODO(developer): Retrieve an Instance ID token for use with FCM.
  // ...
}).catch(function(err) {
  console.log('Unable to get permission to notify.', err);
});

获取注册令牌

本部分介绍如何检索应用实例的注册令牌以及如何监控令牌刷新事件。因为令牌可能会在初次启用后发生轮替,所以您应该监控令牌刷新并始终检索最新更新的注册令牌。

注册令牌可能会在发生下列情况时更改:

  • 网页应用删除注册令牌时。
  • 用户清除浏览器数据时。在这种情况下,调用 getToken 可检索新令牌。

检索当前注册令牌

如果需要检索当前令牌,请调用 getToken。当尚未获得授权时,此方法会返回 null。在其他情况下,它会返回一个令牌,或由于出现错误而拒绝该 Promise。

消息传递服务需要 firebase-messaging-sw.js 文件。除非您已经有 firebase-messaging-sw.js 文件,否则请创建一个使用该名称的空文件,并在检索令牌之前将该文件放在您网域的根目录中。您可以在后面的客户端设置流程中向该文件添加有意义的内容。

要检索当前令牌,请运行以下命令:

// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken().then(function(currentToken) {
  if (currentToken) {
    sendTokenToServer(currentToken);
    updateUIForPushEnabled(currentToken);
  } else {
    // Show permission request.
    console.log('No Instance ID token available. Request permission to generate one.');
    // Show permission UI.
    updateUIForPushPermissionRequired();
    setTokenSentToServer(false);
  }
}).catch(function(err) {
  console.log('An error occurred while retrieving token. ', err);
  showToken('Error retrieving Instance ID token. ', err);
  setTokenSentToServer(false);
});

监控令牌刷新

每当生成新的令牌,系统都会触发 onTokenRefresh 回调函数,因此,在该回调函数的上下文中调用 getToken 可以确保您获取的是当前可用的注册令牌。

// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(function() {
  messaging.getToken().then(function(refreshedToken) {
    console.log('Token refreshed.');
    // Indicate that the new Instance ID token has not yet been sent to the
    // app server.
    setTokenSentToServer(false);
    // Send Instance ID token to app server.
    sendTokenToServer(refreshedToken);
    // ...
  }).catch(function(err) {
    console.log('Unable to retrieve refreshed token ', err);
    showToken('Unable to retrieve refreshed token ', err);
  });
});

获取令牌后,将其发送到您的应用服务器,并使用您首选的方法进行存储。您可以使用 Instance ID server API 来获取有关订阅的信息

为客户端应用订阅主题

有了注册令牌和主题名称,您就可以使用 Google Instance ID server API 向相应主题添加相应令牌。请在以下端点调用 Instance ID API,并提供应用实例的注册令牌和主题名称:

 https://iid.googleapis.com/iid/v1/<REGISTRATION_TOKEN>/rel/topics/<TOPIC_NAME>

例如,要为某个应用实例订阅名为“movies”的主题,请从您的服务器将以下 POST 请求发送到端点,然后在授权标头中添加如下所示的服务器 API 密钥:

https://iid.googleapis.com/iid/v1/nKctODamlM4:CKrh_PC8kIb7O...clJONHoA/rel/topics/movies
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

鉴于服务器密钥的敏感性,切勿从客户端发送这种类型的请求。

请求成功则返回 HTTP 200 OK。如需详细了解错误响应及如何发送批量请求,请参阅为应用实例创建关系映射

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

Node.js

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

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

Java

// 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')

Go

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

利用 Admin FCM API,您还可以通过将注册令牌传递给相应的方法来为设备退订主题:

Node.js

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

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

Java

// 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')

Go

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

subscribeToTopic() 方法和 unsubscribeFromTopic() 方法会返回一个包含 FCM 响应的对象。无论请求中指定的注册令牌的数量为何,返回类型的格式都是相同的。

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

接收和处理主题消息

消息的行为取决于相应页面的状态:页面位于前台(获得焦点)、位于后台、隐藏在其他标签页后,还是完全关闭。无论哪种情况,页面都必须处理 onMessage 回调函数;但当页面位于后台时,您可能还需要处理 setBackgroundMessageHandler 或配置显示通知,让用户能够将您的网页应用放到前台。

应用状态 通知 数据 两者
前台 onMessage onMessage onMessage
后台 (Service Worker) SDK 显示的通知 setBackgroundMessageHandler SDK 显示的通知

在网页应用位于前台时处理消息

为接收 onMessage 事件,您的应用必须在 firebase-messaging-sw.js 中定义 Firebase 消息传递 Service Worker。或者,您也可以使用 useServiceWorker 指定一个现有的 Service Worker。

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');

// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
  'messagingSenderId': 'YOUR-SENDER-ID'
});

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();

当您的应用位于前台时(用户当前正在查看您的网页),您可以直接在页面中接收数据和通知有效负载。

// Handle incoming messages. Called when:
// - a message is received while the app has focus
// - the user clicks on an app notification created by a service worker
//   `messaging.setBackgroundMessageHandler` handler.
messaging.onMessage(function(payload) {
  console.log('Message received. ', payload);
  // ...
});

在网页应用位于后台时处理消息

当应用位于后台时,收到的所有消息都会导致在浏览器中显示通知。您可以在从应用服务器发送请求时指定用于此通知的选项(如标题或点击操作),也可以在客户端中使用 Service Worker 进行指定。

在发送请求中设置通知选项

对于从应用服务器发送的通知消息,FCM JavaScript API 支持 fcm_options.link 键。通常情况下,此键会被设置为网页应用中的某一页面:

https://fcm.googleapis.com//v1/projects/<YOUR-PROJECT-ID>/messages:send
Content-Type: application/json
Authorization: bearer <YOUR-ACCESS-TOKEN>

{
  "message": {
    "topic": "matchday"
    "notification": {
      "title": "Background Message Title",
      "body": "Background message body"
    },
    "webpush": {
      "fcm_options": {
        "link": "https://dummypage.com"
      }
    }
  }
}

如果链接值指向已在某个浏览器标签页中打开的页面,那么当用户点击通知时,系统会将该标签页放到前台。如果相应页面尚未打开,那么当用户点击通知时,系统会在一个新标签页中打开该页面。

由于数据消息不支持 fcm_options.link,因此我们建议您在所有数据消息中添加通知负载。或者,您也可以使用 Service Worker 处理通知。

如需详细了解通知消息与数据消息之间的区别,请参阅消息类型

在 Service Worker 中设置通知选项

不管是通知消息还是数据消息,您都可以在 Service Worker 中设置通知选项。首先,在 Service Worker 中初始化您的应用:

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');

// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
  'messagingSenderId': 'YOUR-SENDER-ID'
});

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();

要设置选项,请调用 firebase-messaging-sw.js 中的 setBackgroundMessageHandler。在本示例中,我们设置了针对标题、正文、图标和点击操作的选项。

messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  var notificationTitle = 'Background Message Title';
  var notificationOptions = {
    body: 'Background Message body.',
    icon: '/firebase-logo.png'
  };

  return self.registration.showNotification(notificationTitle,
    notificationOptions);
});

构建发送请求

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

主题 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 '{
  "notification": {
    "title": "FCM Message",
    "body": "This is a Firebase Cloud Messaging Topic Message!",
  },
  "topic" : "foo-bar"
}' "https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1"

主题 HTTP 响应

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

主题 XMPP 消息

发送至一个主题:

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

主题 XMPP 响应

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

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