发布和订阅

Nearby Messages API 是一种发布-订阅 API,可让附近的设备交换小型数据载荷。设备发布消息后,附近的设备可以接收该消息。消息大小应保持在较小的范围内,以确保良好的性能。此服务不适合交换照片和视频等较大对象。

附近设备集是通过蓝牙和近超声波(听不到)音频交换小令牌来确定的。当设备检测到附近设备的令牌后,会将该令牌发送到 Nearby Messages 服务器进行验证,并检查是否有任何消息要针对应用的当前订阅集进行传送。

应用可以控制用于设备发现的一组介质,以及这些介质是否用于广播令牌和/或扫描令牌。默认情况下,广播和扫描是在所有介质上完成的。如需在部分媒体或介质上进行发现,并控制是否广播或扫描,您必须在创建发布和订阅时传递其他参数。

此库可在 iOS 7 及更高版本上运行,并使用 iOS 8 SDK 构建。

创建消息管理器

此代码会创建一个消息管理器对象,让您可以发布和订阅。消息交换未经身份验证,因此您必须为 iOS 提供公共 API 密钥。您可以使用项目的 Google Developers Console 条目创建一个。

Objective-C

#import <GNSMessages.h>

GNSMessageManager *messageManager =
    [[GNSMessageManager alloc] initWithAPIKey:@"API_KEY"];

Swift

let messageManager = GNSMessageManager(APIKey: "API_KEY")

发布消息

此代码段演示了如何发布包含名称的消息。只要发布对象存在,发布内容就处于有效状态。如需停止发布,请释放发布对象。

Objective-C

id<GNSPublication> publication =
    [messageManager publicationWithMessage:[GNSMessage messageWithContent:[name dataUsingEncoding:NSUTF8StringEncoding]]];

Swift

let publication =
    messageManager.publication(with: GNSMessage(content: name.data(using: .utf8)))

订阅消息

此代码段演示了如何订阅上一个发布代码段共享的所有名称。只要订阅对象存在,订阅就处于有效状态。如需停止订阅,请释放订阅对象。

当发现正在发布消息的附近设备时,系统会调用消息发现处理程序。当不再观测到某条消息时(设备已超出范围或不再发布该消息),系统会调用消息丢失处理程序。

Objective-C

id<GNSSubscription> subscription =
    [messageManager subscriptionWithMessageFoundHandler:^(GNSMessage *message) {
      // Add the name to a list for display
    }
    messageLostHandler:^(GNSMessage *message) {
      // Remove the name from the list
    }];

Swift

let subscription =
    messageManager.subscription(messageFoundHandler: { (message: GNSMessage?) in
      // Add the name to a list for display
    },
    messageLostHandler: { (message: GNSMessage?) in
      // Remove the name from the list
    })

发现媒体

默认情况下,系统会同时使用音频和蓝牙来发现附近的设备,并且会同时通过这两种媒介进行广播和扫描。在某些情况下,您需要将以下条目添加到应用的 Info.plist 中:

  • 如果您的应用使用音频进行扫描,请添加 NSMicrophoneUsageDescription,这是一个字符串,用于说明您将使用麦克风的原因。例如,“麦克风会监听附近设备发送的匿名令牌。”

  • 如果您的应用使用 BLE 进行广播,请添加 NSBluetoothPeripheralUsageDescription,这是一个字符串,用于说明您将通过 BLE 进行通告的原因。例如,“通过蓝牙广播匿名令牌,以发现附近的设备。”

在某些情况下,您的应用可能只需要使用一种介质,并且可能不需要在该介质上同时进行广播和扫描。

例如,如果某应用旨在连接到仅通过音频广播的机顶盒,则只需扫描音频即可发现该机顶盒。以下代码段展示了如何仅使用音频扫描进行发现,从而将消息发布到该机顶盒:

Objective-C

id<GNSPublication> publication = [messageManager publicationWithMessage:message
    paramsBlock:^(GNSPublicationParams *params) {
      params.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams *params) {
        params.discoveryMediums = kGNSDiscoveryMediumsAudio;
        params.discoveryMode = kGNSDiscoveryModeScan;
      }];
    }];

Swift

let publication = messageManager.publication(with: message,
    paramsBlock: { (params: GNSPublicationParams?) in
      guard let params = params else { return }
      params.strategy = GNSStrategy(paramsBlock: { (params: GNSStrategyParams?) in
        guard let params = params else { return }
        params.discoveryMediums = .audio
        params.discoveryMode = .scan
      })
    })

启用调试日志记录

调试日志会将重要的内部事件输出到控制台,这有助于您在将 Nearby Messages 集成到应用中时跟踪可能遇到的问题。如果您联系我们寻求技术支持,我们会要求您提供这些日志。

您应在创建消息管理器之前启用它。此代码段展示了如何启用调试日志记录:

Objective-C

[GNSMessageManager setDebugLoggingEnabled:YES];

Swift

GNSMessageManager.setDebugLoggingEnabled(true)

跟踪“附近的设备”权限状态

需要征得用户同意才能启用设备发现功能。这由“附近”权限状态指示。在首次调用以创建发布或订阅时,系统会向用户显示一个意见征求对话框。如果用户不同意,设备发现功能将无法正常运行。在这种情况下,您的应用应显示一条消息,提醒用户设备发现功能已停用。权限状态存储在 NSUserDefaults 中。

以下代码段演示了如何订阅权限状态。每当权限状态发生变化时,系统都会调用权限状态更改处理程序,并且在用户授予或拒绝权限之前,系统不会在首次调用时调用该处理程序。 释放权限对象以停止订阅。

Objective-C

GNSPermission *nearbyPermission = [[GNSPermission alloc] initWithChangedHandler:^(BOOL granted) {
  // Update the UI here
}];

Swift

let nearbyPermission = GNSPermission(changedHandler: { (granted: Bool) in
  // Update the UI here
})

您的应用可以提供一种方式,让用户更改权限状态;例如,在设置页面上使用切换开关。

以下示例展示了如何获取和设置权限状态。

Objective-C

BOOL permissionState = [GNSPermission isGranted];
[GNSPermission setGranted:!permissionState];  // toggle the state

Swift

let permissionState = GNSPermission.isGranted()
GNSPermission.setGranted(!permissionState)  // toggle the state

跟踪影响“附近”功能的用户设置

如果用户拒绝授予麦克风权限、拒绝授予蓝牙权限或关闭了蓝牙,附近功能将无法正常运行,甚至可能根本无法运行。在这些情况下,您的应用应显示一条消息,提醒用户 Nearby 的运行受到阻碍。以下代码段展示了如何在创建消息管理器时通过传递处理程序来跟踪这些用户设置的状态:

Objective-C

GNSMessageManager *messageManager = [[GNSMessageManager alloc]
    initWithAPIKey:API_KEY
       paramsBlock:^(GNSMessageManagerParams *params) {
         params.microphonePermissionErrorHandler = ^(BOOL hasError) {
           // Update the UI for microphone permission
         };
         params.bluetoothPowerErrorHandler = ^(BOOL hasError) {
           // Update the UI for Bluetooth power
         };
         params.bluetoothPermissionErrorHandler = ^(BOOL hasError) {
           // Update the UI for Bluetooth permission
         };
}];

Swift

let messageManager = GNSMessageManager(
         APIKey: API_KEY,
    paramsBlock: { (params: GNSMessageManagerParams?) in
      guard let params = params else { return }
      params.microphonePermissionErrorHandler = { (hasError: Bool) in
        // Update the UI for microphone permission
      }
      params.bluetoothPowerErrorHandler = { (hasError: Bool) in
        // Update the UI for Bluetooth power
      }
      params.bluetoothPermissionErrorHandler = { (hasError: Bool) in
        // Update the UI for Bluetooth permission
      }
    })

替换“附近的设备”权限对话框

根据您传递给发布和订阅的参数,iOS 可能会要求您授予各种权限,然后才允许 Nearby 正常运行。例如,默认策略会监听通过近超声波音频传输的数据,因此 iOS 会请求使用麦克风的权限。在这些情况下,“附近”会显示一个“预飞行”对话框,说明为何要求用户授予权限。

如果您想提供自定义的“预检”对话框,请将 permissionRequestHandler 参数设置为发布或订阅参数中的自定义块。在用户做出响应后,您的自定义块必须调用 permissionHandler 块。以下代码段展示了如何针对发布内容执行此操作:

Objective-C

id<GNSPublication> publication =
    [messageManager publicationWithMessage:[GNSMessage messageWithContent:[name dataUsingEncoding:NSUTF8StringEncoding]]
                               paramsBlock:^(GNSPublicationParams *params) {
                                 params.permissionRequestHandler = ^(GNSPermissionHandler permissionHandler) {
                                   // Show your custom dialog here.
                                   // Don't forget to call permissionHandler() with YES or NO when the user dismisses it.
                                 };
                               }];

Swift

let publication =
    messageManager.publication(with: GNSMessage(content: name.data(using: .utf8)),
        paramsBlock: { (params: GNSPublicationParams?) in
          guard let params = params else { return }
          params.permissionRequestHandler = { (permissionHandler: GNSPermissionHandler?) in
            // Show your custom dialog here.
            // Don't forget to call permissionHandler() with true or false when the user dismisses it.
          }
        })

后台操作

使用 BLE 进行设备发现的发布和订阅可以在后台运行。在决定使用后台模式时,您应注意以下事项:

  • 后台操作必须仅使用 BLE 介质;不支持音频。
  • 后台 BLE 会增加电池消耗。费用较低,但在决定使用后台模式之前,您应先进行衡量。
  • iOS 会询问用户是否允许通过 BLE 在后台进行广告宣传。

如需向发布或订阅添加后台模式,请按照以下额外步骤操作:

  • 通过传入配置正确的 GNSStrategy 对象,在发布或订阅中启用后台模式和仅 BLE 模式。以下代码段展示了如何针对订阅执行此操作:

    Objective-C

    id<GNSSubscription> subscription =
        [messageManager subscriptionWithMessageFoundHandler:^(GNSMessage *message) {
          // Add the name to a list for display
        }
        messageLostHandler:^(GNSMessage *message) {
          // Remove the name from the list
        }
        paramsBlock:^(GNSSubscriptionParams *params) {
          params.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams *params) {
            params.allowInBackground = YES;
            params.discoveryMediums = kGNSDiscoveryMediumsBLE;
          }];
        }];
    

    Swift

    let subscription =
        messageManager.subscription(messageFoundHandler: { (message: GNSMessage?) in
          // Add the name to a list for display
        },
        messageLostHandler: { (message: GNSMessage?) in
          // Remove the name from the list
        },
        paramsBlock:{ (params: GNSSubscriptionParams?) in
          guard let params = params else { return }
          params.strategy = GNSStrategy(paramsBlock: { (params: GNSStrategyParams?) in
            guard let params = params else { return }
            params.allowInBackground = true
            params.discoveryMediums = .BLE
          })
        })
    

  • 将以下条目添加到应用的 Info.plist 中:

    • UIBackgroundModes 个条目:

      • bluetooth-central 用于在后台进行 BLE 扫描。仅当发现模式包含扫描时需要;默认情况下,发现模式包含扫描。
      • bluetooth-peripheral 用于在后台进行 BLE 广播。仅当发现模式包含广播时需要;默认情况下,发现模式包含广播。
    • NSBluetoothPeripheralUsageDescription 用于描述您将在 BLE 上投放广告的原因的字符串。例如,“通过蓝牙广播匿名令牌,以发现附近的设备。”如需了解详情,请参阅 Apple 的文档

  • 应用在后台运行时,可能会随时被系统终止。如果后台模式是一项可由用户启用或停用的设置,您的应用应执行以下操作:

    • 每当用户更改后台模式值时,都将其保存到 NSUserDefaults
    • 在启动时,从 NSUserDefaults 读取该值,并在启用后台模式时恢复附近的发布和/或订阅。

后台通知

如果您希望应用在后台运行时,当订阅收到消息时通知用户,可以使用本地通知

如需将它们添加到您的应用中,请按以下步骤操作:

  • 在启动时注册本地通知:

    Objective-C

    if ([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]) {
      [[UIApplication sharedApplication] registerUserNotificationSettings:
          [UIUserNotificationSettings settingsForTypes:
              UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound
                                            categories:nil]];
    }
    

    Swift

    UIApplication.shared.registerUserNotificationSettings(
        UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))
    

  • 在订阅的 message-found 处理程序中发送本地通知:

    Objective-C

    GNSMessageHandler myMessageFoundHandler = ^(GNSMessage *message) {
        // Send a local notification if not in the foreground.
        if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
          UILocalNotification *localNotification = [[UILocalNotification alloc] init];
          localNotification.alertBody = @"Message received";
          [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
        }
        // Process the new message...
      };
    

    Swift

    let myMessageFoundHandler: GNSMessageHandler = { (message: GNSMessage?) in
      // Send a local notification if not in the foreground.
      if UIApplication.shared.applicationState != .active {
        let localNotification = UILocalNotification()
        localNotification.alertBody = "Message received"
        UIApplication.shared.presentLocalNotificationNow(localNotification)
      }
      // Process the new message...
    }