转到控制台

在 iOS 上向多台设备发送消息

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

  • 主题消息传递,允许您将消息发送至已经选择订阅特定主题的多台设备。
  • 设备组消息传递,允许您将消息发送至属于同一组的设备上运行的多个应用实例。

本教程主要介绍如何使用 FCM 的 HTTP 协议或 XMPP 协议从应用服务器发送主题消息,以及如何在 iOS 应用中接收和处理此类消息。本页面列出了实现上述目标所需的从设置到验证的所有步骤。如果您已针对 FCM 设置 iOS 客户端应用,或已经完成发送第一条消息所需的步骤,则本页面所列步骤中可能包括了您已经完成的步骤。

将 Firebase 添加至您的 iOS 项目

如果您已经为自己的应用启用了其他 Firebase 功能,那么您可能已经完成了本部分将要介绍的一些任务。您需要专门为 FCM 上传您的 APN 身份验证密钥注册接收远程通知

前提条件

  • 安装 Xcode 10.1 或更高版本。

  • 安装 CocoaPods 1.4.0 或更高版本。

  • 在 Xcode 中打开您的项目。

    • 您的项目必须适用于 iOS 8 或更高版本。

    • Swift 项目必须使用 Swift 3.0 或更高版本。

  • 设置用于运行您的应用的一台 iOS 真机设备或 iOS 模拟器。

    • 为了支持云消息传递,您需要:

      • 一台 iOS 真机设备
      • 一个 Apple 开发者帐号所对应的 Apple 推送通知身份验证密钥
      • 在 Xcode 中通过 App > Capabilities 启用推送通知功能。
    • 对于所有其他 Firebase 产品,您可以使用 iOS 真机设备或 iOS 模拟器。

  • 使用您的 Google 帐号登录 Firebase

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

创建 Firebase 项目

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

使用 Firebase 注册您的应用

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

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

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

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

  2. iOS 软件包 ID 字段中输入应用的软件包 ID

    • 在 XCode 中打开您的应用,然后在顶级 Runner 目录中访问常规标签页,找到此软件包 ID。软件包标识符字段的值是 iOS 软件包 ID(例如 com.yourcompany.yourproject)。
  3. (可选)根据设置工作流的提示输入其他应用信息。

  4. 点击注册应用

添加 Firebase 配置文件

  1. 点击下载 GoogleService-Info.plist 以获取 Firebase iOS 配置文件 (GoogleService-Info.plist)。

  2. 将配置文件移至 Xcode 项目的根目录中。如果出现提示,请选择将配置文件添加到所有目标。

如果您的项目中有多个软件包 ID,则必须将每个软件包 ID 与 Firebase 控制台中的注册应用相关联,使每个应用都有自己的 GoogleService-Info.plist 文件。

将 Firebase SDK 添加至您的应用

我们建议使用 CocoaPods 安装 Firebase 库。如果不想使用 CocoaPods,则可以直接集成 SDK 框架

请注意,如果您使用的是某个快速入门示例,示例中会提供 Xcode 项目和 Podfile(带有 pod),不过您还是需要添加 Firebase 配置文件并安装 pod。

您可以将任何受支持的 Firebase 产品添加到 iOS 应用中。

  1. 如果您没有 Podfile,请创建一个:

    cd your-project-directory
    pod init
  2. 将想要用在应用中的 pod 添加到 Podfile。

    pod 'Firebase/Core'
    pod 'Firebase/Messaging'

    此 pod 会在 iOS 应用中添加 Firebase 正常运行所需的必备库。

  3. 安装 pod,然后打开 .xcworkspace 文件以便在 Xcode 中查看该项目:

    pod install
    open your-project.xcworkspace

上传您的 APNs 身份验证密钥

将您的 APNs 身份验证密钥上传到 Firebase。如果您还没有 APNs 身份验证密钥,请参阅配置 FCM APNs

  1. 在 Firebase 控制台中,在您的项目内依次选择齿轮图标、项目设置以及云消息传递标签。

  2. iOS 应用配置下的 APNs 身份验证密钥中,点击上传按钮。

  3. 转到您保存密钥的位置,选择该密钥,然后点击打开。添加该密钥的密钥 ID(可在 Apple Developer Member CenterCertificates, Identifiers & Profiles 中找到),然后点击上传

在您的应用中初始化 Firebase

您需要为应用添加 Firebase 初始化代码。请按如下所示导入 Firebase 模块并配置一个共享实例:

  1. UIApplicationDelegate 中导入 Firebase 模块:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  2. 配置一个 FirebaseApp 共享实例(通常在应用的 application:didFinishLaunchingWithOptions: 方法中配置):

    Swift

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Objective-C

    // Use Firebase library to configure APIs
    [FIRApp configure];

注册接收远程通知

可在启动时或在应用流程中计划的时间注册您的应用,以便接收远程通知。按如下所示调用 registerForRemoteNotifications

Swift

if #available(iOS 10.0, *) {
  // For iOS 10 display notification (sent via APNS)
  UNUserNotificationCenter.current().delegate = self

  let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
  UNUserNotificationCenter.current().requestAuthorization(
    options: authOptions,
    completionHandler: {_, _ in })
} else {
  let settings: UIUserNotificationSettings =
  UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
  application.registerUserNotificationSettings(settings)
}

application.registerForRemoteNotifications()

Objective-C

if ([UNUserNotificationCenter class] != nil) {
  // iOS 10 or later
  // For iOS 10 display notification (sent via APNS)
  [UNUserNotificationCenter currentNotificationCenter].delegate = self;
  UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
      UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
  [[UNUserNotificationCenter currentNotificationCenter]
      requestAuthorizationWithOptions:authOptions
      completionHandler:^(BOOL granted, NSError * _Nullable error) {
        // ...
      }];
} else {
  // iOS 10 notifications aren't available; fall back to iOS 8-9 notifications.
  UIUserNotificationType allNotificationTypes =
  (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
  UIUserNotificationSettings *settings =
  [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
  [application registerUserNotificationSettings:settings];
}

[application registerForRemoteNotifications];

为客户端应用订阅主题

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

要订阅一个主题,请从应用的主线程中调用订阅方法(FCM 并非是线程安全的)。如果一开始订阅请求失败,FCM 会自动重试。对于无法完成订阅的情况,订阅会引发一个错误,您可以在完成处理程序中捕获该错误,如下所示:

Swift

Messaging.messaging().subscribe(toTopic: "weather") { error in
  print("Subscribed to weather topic")
}

Objective-C

[[FIRMessaging messaging] subscribeToTopic:@"weather"
                                completion:^(NSError * _Nullable error) {
  NSLog(@"Subscribed to weather topic");
}];

此调用会向 FCM 后端发出一个异步请求,并为该客户端订阅指定主题。在调用 subscribeToTopic:topic 之前,请确保客户端应用实例已通过回调函数 didReceiveRegistrationToken 收到注册令牌。

每次应用启动时,FCM 都会确保所有已请求主题均已订阅。若要退订,可调用 unsubscribeFromTopic:topic,FCM 即会在后台中退订相关主题。

接收和处理主题消息

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

按如下所示实现 AppDelegate application:didReceiveRemoteNotification:

Swift

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // Messaging.messaging().appDidReceiveMessage(userInfo)

  // Print message ID.
  if let messageID = userInfo[gcmMessageIDKey] {
    print("Message ID: \(messageID)")
  }

  // Print full message.
  print(userInfo)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // Messaging.messaging().appDidReceiveMessage(userInfo)

  // Print message ID.
  if let messageID = userInfo[gcmMessageIDKey] {
    print("Message ID: \(messageID)")
  }

  // Print full message.
  print(userInfo)

  completionHandler(UIBackgroundFetchResult.newData)
}

Objective-C

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // [[FIRMessaging messaging] appDidReceiveMessage:userInfo];

  // Print message ID.
  if (userInfo[kGCMMessageIDKey]) {
    NSLog(@"Message ID: %@", userInfo[kGCMMessageIDKey]);
  }

  // Print full message.
  NSLog(@"%@", userInfo);
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // [[FIRMessaging messaging] appDidReceiveMessage:userInfo];

  // Print message ID.
  if (userInfo[kGCMMessageIDKey]) {
    NSLog(@"Message ID: %@", userInfo[kGCMMessageIDKey]);
  }

  // Print full message.
  NSLog(@"%@", userInfo);

  completionHandler(UIBackgroundFetchResultNewData);
}

构建发送请求

您创建一个主题后,可以通过两种方法向主题发送消息:一是在客户端为客户端应用实例订阅主题,二是通过服务器 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

后续步骤