在 Apple 平台上向多台設備發送消息

Firebase 雲消息傳遞提供了兩種將消息定位到多個設備的方法:

  • 主題消息傳遞,它允許您向已選擇加入特定主題的多個設備發送消息。
  • 設備群組消息,它允許您向在屬於一個群組的設備上運行的應用程序的多個實例發送一條消息。

本教程重點介紹使用適用於 FCM 的Admin SDKREST API從您的應用服務器發送主題消息,以及在 Apple 應用中接收和處理它們。此頁面列出了從設置到驗證的所有步驟——因此,如果您為 FCM設置了 Apple 客戶端應用程序或完成了發送您的第一條消息的步驟,它可能涵蓋您已經完成的步驟。

將 Firebase 添加到您的 Apple 項目

如果您已經為您的應用啟用了其他 Firebase 功能,本部分將介紹您可能已完成的任務。特別是對於 FCM,您需要上傳您的 APNs 身份驗證密鑰註冊遠程通知

先決條件

  • 安裝以下內容:

    • Xcode 13.3.1 或更高版本
  • 確保您的項目滿足以下要求:

    • 您的項目必須針對這些平台版本或更高版本:
      • iOS 10
      • macOS 10.12
      • 電視操作系統 12
      • 手錶操作系統 6
  • 設置物理 Apple 設備來運行您的應用程序,並完成以下任務:

    • 為您的Apple Developer 帳戶獲取 Apple Push Notification Authentication Key。
    • 在 XCode 中的App > Capabilities下啟用推送通知。

如果您還沒有 Xcode 項目並且只想試用 Firebase 產品,您可以下載我們的快速入門示例之一。

創建一個 Firebase 項目

在將 Firebase 添加到您的 Apple 應用之前,您需要創建一個 Firebase 項目以連接到您的應用。請訪問了解 Firebase 項目以了解有關 Firebase 項目的更多信息。

向 Firebase 註冊您的應用

要在您的 Apple 應用中使用 Firebase,您需要在您的 Firebase 項目中註冊您的應用。註冊您的應用程序通常稱為將您的應用程序“添加”到您的項目中。

  1. 轉到Firebase 控制台

  2. 在項目概覽頁面的中心,單擊iOS+圖標以啟動設置工作流程。

    如果您已將應用添加到 Firebase 項目,請點擊添加應用以顯示平台選項。

  3. 捆綁包 ID字段中輸入您應用的捆綁包 ID。

  4. (可選)輸入其他應用信息:應用暱稱應用商店ID

  5. 點擊註冊應用

添加 Firebase 配置文件

  1. 單擊下載 GoogleService-Info.plist以獲取您的 Firebase Apple 平台配置文件 ( GoogleService-Info.plist )。

  2. 將配置文件移動到 Xcode 項目的根目錄中。如果出現提示,請選擇將配置文件添加到所有目標。

如果您的項目中有多個捆綁包 ID,則必須將每個捆綁包 ID 與 Firebase 控制台中的註冊應用相關聯,以便每個應用都可以擁有自己的GoogleService-Info.plist文件。

將 Firebase SDK 添加到您的應用

使用 Swift Package Manager 安裝和管理 Firebase 依賴項。

  1. 在 Xcode 中,打開您的應用項目,導航到File > Add Packages
  2. 出現提示時,添加 Firebase Apple 平台 SDK 存儲庫:
  3.   https://github.com/firebase/firebase-ios-sdk
  4. 選擇 Firebase 雲消息庫。
  5. 為了獲得 Firebase 雲消息傳遞的最佳體驗,我們建議在您的 Firebase 項目中啟用 Google Analytics ,並將 Firebase SDK for Google Analytics 添加到您的應用中。您可以選擇沒有 IDFA 集合或有 IDFA 集合的庫。
  6. 完成後,Xcode 將在後台自動開始解析和下載您的依賴項。

上傳您的 APNs 身份驗證密鑰

將您的 APNs 身份驗證密鑰上傳到 Firebase。如果您還沒有 APNs 身份驗證密鑰,請確保在Apple Developer Member Center中創建一個。

  1. 在 Firebase 控制台的項目中,選擇齒輪圖標,選擇Project Settings ,然後選擇Cloud Messaging選項卡。

  2. iOS app configuration下的APNs authentication key中,單擊Upload按鈕。

  3. 瀏覽到您保存密鑰的位置,選擇它,然後單擊打開。添加密鑰的密鑰 ID(可在Apple Developer Member Center中獲得),然後單擊Upload

在您的應用中初始化 Firebase

您需要將 Firebase 初始化代碼添加到您的應用程序。導入 Firebase 模塊並配置共享實例,如下所示:

  1. 在您的UIApplicationDelegate中導入FirebaseCore模塊,以及您的應用委託使用的任何其他Firebase 模塊。例如,要使用 Cloud Firestore 和身份驗證:

    迅速

    import FirebaseCore
    import FirebaseFirestore
    import FirebaseAuth
    // ...
          

    Objective-C

    @import FirebaseCore;
    @import FirebaseFirestore;
    @import FirebaseAuth;
    // ...
          
  2. 配置FirebaseApp共享實例,通常在App的初始化程序或應用委託的application(_:didFinishLaunchingWithOptions:)方法中:

    迅速

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

    Objective-C

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

註冊遠程通知

無論是在啟動時,還是在應用程序流程中的所需位置,註冊您的應用程序以獲取遠程通知。調用registerForRemoteNotifications如圖:

迅速

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 會自動重試。對於無法完成訂閱的情況,訂閱會引發錯誤,您可以在完成處理程序中捕獲該錯誤,如下所示:

迅速

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 以與其他下游消息相同的方式傳遞主題消息。

實現application(_:didReceiveRemoteNotification:fetchCompletionHandler:)如圖:

迅速

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
    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 full message.
  NSLog(@"%@", userInfo);

  completionHandler(UIBackgroundFetchResultNewData);
}

構建發送請求

創建主題後,通過在客戶端訂閱客戶端應用程序實例或通過服務器 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

下一步