iOS开发精进

Core Bluetooth Background Proces

2016-05-07  本文已影响729人  dream小恩

Apple 官方文档 Core Bluetooth Programming Guide 的中文翻译
本文翻译的是其中的第二章节 Performing Common Central Role Tasks
一些术语:
Bluetooth low energe(BLE):低功耗蓝牙,全文默认的蓝牙版本
central:中央设备
peripheral:外围设备,[ pəˈrɪfərəl ]重音在第二音节,千万不要重音放在第一音节。会念了之后发现这个单词的发音特别有韵味有没有。
service:服务,peripheral 暴露的服务
characteristic:特性,一个 service 中包含若干特性,[ ˌkærəktəˈrɪstɪk ]同样的需要注意重音。
这些术语将不再做翻译,以避免翻译不同带来的沟通壁垒。
另外一些开发过程中习以为常用英文表达的词,如manager, delegate, framework等也保持不翻译状态,以便于理解为要。
国际惯例,转载请注明出处:http://www.jianshu.com/p/4f0583bce5a9

// pragma mark - 正文开始

Core Bluetooth Background Processing for iOS Apps

Core Bluetooth 后台处理

对iOS应用来说,知道你的应用是在前台运行还是后台运行非常关键。由于iOS设备的系统资源非常有限,当应用处于后台时必须表现的与在前台时相当不同。关于iOS多任务处理的综合讨论,可以看 App Programming Guide for iOS 中的 App States and Multitasking

默认情况下,当应用在后台或处于挂起状态(suspended state)时,许多 Core Bluetooth 的常规任务--包含 central 端的和 peripheral 端的--都是被禁用的。即便如此,你可以通过申明你的应用支持 Core Bluetooth 后台执行模式来允许你的应用在处理某些蓝牙相关事件时从挂起状态唤醒。即使你的应用不需要全时段的后台处理支持,它仍然可以要求系统在发生重要事件时通知它。

然而即使你的应用支持了一种或多种后台处理模式,它仍然不能保证一直运行。某些时刻,比如说,系统可能出于释放更多内存以支持当前前台应用的需要终止你的应用,这将导致任何活动的或者待处理的连接丢失。自iOS 7起,Core Bluetooth 支持为 central 和 peripheral manager 对象保存状态信息以及在应用启动时恢复这些状态。你可以用这个特性支持有长期任务处理需求的蓝牙设备。

Foreground-Only Apps

纯前台应用

就大多数iOS应用而言,除非你请求了执行后台任务的许可,否则你的应用在进入后台后不久就会转到挂起状态。在挂起状态中,你的应用无法处理蓝牙相关任务,也无法感知蓝牙相关事件,直到它恢复到前台。

在 central 端,当处于后台或挂起状态中时,纯前台应用--那些没有声明支持任意一种蓝牙后台处理模式的应用--无法扫描或者发现正在广播的 peripheral。 在 peripheral 端,广播被禁用,任何 central 如果尝试访问已发布 service 中的动态 characteristic 值会返回错误。

对于不同使用场景,这样的默认行为会从几个方面影响你的应用。举例来说,假设你连接上了一个 peripheral,正在交互数据。现在假设你的应用进入挂起状态(因为,比如用户切换到另一个应用)。如果在你的应用挂起时和 peripheral 的连接断开了,你无法获知到连接断开的发生,直到你的应用回到前台。

利用 Peripheral 连接选项

当纯前台应用处于挂起状态时,所有蓝牙相关事件被系统存到队列中,直到应用回到前台才会递送它。即使如此,Core Bluetooth 提供了一种提示用户的途径,当某些 central 端事件发生之时。用户于是可以用这些提示决定某个事件是否足够需要将应用唤至前台。

你可以这样使用这些提示:在调用 CBCentralManager 类的 connectPeripheral:options: 方法连接远程 peripheral 时包含下列 peripheral 连接选项中的一种。

Core Bluetooth Background Execution Modes

Core Bluetooth 后台执行模式

如果你的应用需要在后台运行,执行某些蓝牙相关任务,它必须在 Information property list (Info.plist) 文件中声明它支持一种 Core Bluetooth 后台执行模式。当你的应用声明了这个,系统会将它从挂起状态唤醒使它能够处理蓝牙相关事件。这项支持对于需要定期和提供数据的蓝牙设备进行交互的应用很重要,例如心率监测。

有两种应用可以声明的 Core Bluetooth 后台执行模式 -- 一种给实现了 central 角色的应用,另一种给实现了 peripheral 角色的应用。如果你的应用两个角色都实现,它也可以声明两种模式都支持。

要声明 Core Bluetooth 后台执行模式,需要在 Info.plist 文件中 添加 UIBackgroundModes 键,并将值设成包含下列字符串之一的数组。

注意:Xcode 中的 property list 编辑器默认将许多键名显示为便于人阅读的字符串,而非实际的键名。(注1)要显示如同 Info.plist 中的实际键名,在编辑器窗口中 Control+单击任意键,在弹出菜单中启用 Show Raw Keys/Values。

关于如何配置 Info.plist 文件内容的更多信息,请看 Property List Editor Help

bluetooth-central 后台执行模式

当一个实现了 central 角色的应用在它的 Info.plist 文件中包含 UIBackgroundModes 键以及 bluetooth-central 值,Core Bluetooth framework 将允许你的应用在后台运行,执行某些蓝牙相关操作。当你的应用在后台,你仍然可以发现并连接 peripheral,探索和交互 peripheral 数据。而且,任何 CBCentralManagerDelegateCBPeripheralDelegate delegate 方法被调用时,系统会唤醒你的应用,允许它处理重要的 central 角色事件,例如当连接建立或销毁时,当一个 peripheral 发送 characteristic 更新值,以及当 central manager 的状态改变时。

虽然在后台时你的应用也可以执行许多蓝牙相关任务,但要记住,在后台时扫描 peripheral 的操作和在前台时有所不同。具体而言,当你的应用在后台扫描设备时:

The bluetooth-peripheral 后台执行模式

当处于后台时若想要执行某些 peripheral 角色任务,你必须在 Info.plist 文件中包含 UIBackgroundModes 键并设置了 bluetooth-peripheral 值。当应用的 Info.plist 文件中包含了这个键值对时,系统才会唤醒你的应用,去处理读、写和订阅事件。

除了允许你的应用在收到读写订阅事件时被唤醒之外,Core Bluetooth framework 还允许应用在后台时发送广播。即使如此,你需要注意,应用处于后台时的广播与处于前台时有所不同。具体而言,处于后台时应用的广播:

Use Background Execution Modes Wisely

聪明的使用后台执行模式

虽然有时为了实现某项功能,必须声明你的应用支持 Core Bluetooth 后台处理模式,之一或两种都是,但是你总是需要负责任的使用后台处理。因为处理许多蓝牙相关任务需要激活 iOS 设备的内置广播(onboard radio),广播使用会缩短 iOS 设备的电池寿命,尝试将后台任务量最小化。蓝牙事件唤醒应用之后,应用应该尽快处理并返回,这样应用才能再次挂起。

任何声明支持任一蓝牙后台模式的应用,必须遵循一些基本原则:

关于应用在后台时行为的更多通用信息,参见 App Programming Guide for iOS 中的 做一个负责任的后台应用(Being a Responsible Background App)

Performing Long-Term Actions in the Background

在后台运行长期任务

状态保存及恢复

一些应用可能需要使用 Core Bluetooth framework 在后台执行长期任务。比如说,你正在开发一款家庭安全应用,它使用 iOS 设备与装备了BLE技术的门锁进行通信。应用和门锁之间的交互能够自动做到用户出门时自动锁门、用户回家时自动开锁,所有这些操作都是在后台完成。当用户出门时,iOS 设备离开门锁的距离过远时,和门锁之间的连接就会断开。此时,应用可以调用 CBCentralManager 类的 connectPeripheral:options: 方法,如果连接请求没有超时,iOS 设备就会重新连上。

现在假设用户要出差几天。如果应用在这期间被系统终止了,用户回家时应用就没法和门锁重连上,用户就没法自动开门。对这类应用来说,能用 Core Bluetooth 执行长期任务就成了关键因素,比如监视活动和挂起连接。

为状态保存及恢复添加支持

在 Core Bluetooth 中,状态保存和恢复是一项可选特性,且需要应用的配合。你可以通过下列步骤添加这项特性支持:

  1. (必选)在为 central 或 peripheral manager 对象分配内存和初始化这个时机选择使用状态保持和恢复机制。这个步骤在下文详述:选择使用状态保存和恢复机制(Opt In to State Preservation and Restoration)
  2. (必选)当你的应用被系统重新唤起时,重新实例化任何 central 或 peripheral manager 对象。这个步骤在下文详述:Reinstantiate Your Central and Peripheral Managers
  3. (必选)实现恰当的恢复时 delegate 方法。这个步骤在下文详述:Implement the Appropriate Restoration Delegate Method
  4. (可选)更新 central 和 peripheral manager 对象的初始化操作。这个步骤在下文详述:Update Your Initialization Process
    注:四个点对应本节下文展开的四个小节
选择使用状态保存和恢复机制

想要选择使用状态保存和恢复机制,只需简单的在分配内存初始化 central 或 peripheral manager 时,提供一个唯一的恢复标识(restoration identifier)。恢复标识是一个字符串,为 Core Bluetooth 及你的应用标识了 central 和 peripheral manager 对象。这个字符串的值仅对你的应用有意义,但是这个字符串的存在告诉 Core Bluetooth 它需要保存标记对象的状态。Core Bluetooth 只会保存那些有恢复标识的对象的状态。

举例来说,在仅有一个 CBCentralManager 对象实例,实现了 central 角色的应用中,选择使用状态保存和恢复机制,可以在分配内存初始化 central manager 时为其指定一个 CBCentralManagerOptionRestoreIdentifierKey 选项,并提供一个恢复标识。

    myCentralManager =
        [[CBCentralManager alloc] initWithDelegate:self queue:nil
        options:@{ CBCentralManagerOptionRestoreIdentifierKey:
        @"myCentralManagerIdentifier" }];

为 peripheral manager 对选择使用这个机制也是用类似的方法,虽然上面的代码没有演示:为每个 peripheral manager 对象分配内存初始化时,指定 CBPeripheralManagerOptionRestoreIdentifierKey 初始化选项,并提供一个恢复标识。

注意:由于应用可以有多个 CBCentralManager
CBPeripheralManager 对象实例,请保证每个恢复标识的唯一性,这样系统才能正确地把他们区分开。

重新实例化 Central 和 Peripheral Managers

当你的应用被系统重新启动到后台,你要做的第一件事就是,用相同的恢复标识重新实例化 central 和 peripheral manager。如果你的应用仅有一个 central 或 peripheral manager,而且这个 manager 一直存在于应用中,这一步你就不需要什么别的了。

如果你的应用使用了多个 central 或 periphral managers,或者它使用的 manager 不是一直存在于应用中,你的应用还需要知道,当它被系统重启时,具体该重新实例化哪个 manager。你可以访问一个恢复标识的列表,记录了应用被终止时系统为你保存的所有 manager 对象。只需这样设置:在实现应用的application:didFinishLaunchingWithOptions: delegate 方法时使用恰当的启动选项键值(launch option keys)UIApplicationLaunchOptionsBluetoothCentralsKey 或者 UIApplicationLaunchOptionsBluetoothPeripheralsKey

例如,当你的应用被系统重新启动,你可以通过如下方法取回所有系统帮你保存的 central manager 对象恢复标识:

    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

        NSArray *centralManagerIdentifiers =
            launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
        ...

拿到恢复标识的列表之后,简单的遍历他,重新实例化合适的 central manager 对象。

注意:当你的应用被重新启动时,系统仅会为处理蓝牙相关任务的 central 和 peripheral manager 对象提供恢复标识。这些启动选项键在 UIApplicationDelegate Protocol Reference 中有详细描述。

实现恰当的恢复回调方法

当你在应用中重新实例化了恰当的 central 和 peripheral manager,下一步任务就是通过把他们的状态和蓝牙系统同步以实现状态恢复。为了让应用知道系统在它没有运行时替它做了什么,你需要实现恰当的恢复回调方法。对 central manager 来说,实现 centralManager:willRestoreState: delegate 方法。对 peripheral manager 来说,实现 peripheralManager:willRestoreState: delegate 方法。

重要:对于那些选择使用了 Core Bluetooth 的状态保存与恢复特性的应用来说,当你的应用被重新启动到后台去完成蓝牙相关任务时,这两个方法(centralManager:willRestoreState:peripheralManager:willRestoreState:)会首先被调用。对于那些不选择使用状态保存(或重启时也没有什么需要恢复)的应用来说,centralManagerDidUpdateState:peripheralManagerDidUpdateState: delegate 方法会首先被调用。

对上述两种 delegate 方法来说,相同的是最后一个参数是一个包含应用终止时被系统保存下来的 manager 信息的 dictionary。这个 dictionary 的 key 的列表,请见 CBCentralManagerDelegate Protocol Reference 中的 Central Manager State Restoration Options 常量及 CBPeripheralManagerDelegate Protocol Reference 中的 Peripheral_Manager_State_Restoration_Options 常量。

要恢复 CBCentralManager 对象的状态,在 centralManager:willRestoreState: delegate 方法中使用 key 从 dictionary 中去取。举例,若当你的应用终止时,你的 central manager 对象还有活动或挂起的连接,系统会替你的应用继续监视它们。如下所示,你可以通过使用 CBCentralManagerRestoredStatePeripheralsKey 这个 dictionary key 拿到所有 peripheral 的列表,包含当时 central manager 连接着的或者正在尝试连接的。

    - (void)centralManager:(CBCentralManager *)central
        willRestoreState:(NSDictionary *)state {
        NSArray *peripherals =
            state[CBCentralManagerRestoredStatePeripheralsKey];
        ...

你拿到恢复的 peripheral 列表后做什么取决于用例。举例,如果你的应用维护了一个 central manager 所发现的 peripheral 的列表,你可能想要将恢复的 peripheral 添加到那个列表,才能保证有引用指向他们。如 Connecting to a Peripheral Device After You’ve Discovered It 中所描述的,请确保设置一个 peripheral 的 delegate 以保证它能收到合适的回调。

你可以用相同的方法恢复 CBPeripheralManager 对象的状态,即在 peripheralManager:willRestoreState: delegate 方法中用对应的 key 从 dictionary 中取。

更新初始化过程

在你实现了前面三个必须步骤之后,你可能想要关注一下更新 central 和 peripheral manager 的初始化过程。虽然这是一个可选步骤,它也可能是应用运行正常的重要保证。举例来说,你的应用被终止时可能正在一个已连接的 peripheral 上探索数据过程中。当你的应用带着这个 peripheral 恢复时,它不会知道当时被终止时这个发现过程已经进行了多少。你会希望确保你的发现过程从头开始。

举例,当在 centralManagerDidUpdateState: delegate 方法中初始化你的应用时,你可以找出你是否已经从一个恢复的 peripheral 成功的发现了某个特定 service(当你的应用被终止时),如此:

    NSUInteger serviceUUIDIndex =
        [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,
        NSUInteger index, BOOL *stop) {
            return [obj.UUID isEqual:myServiceUUIDString];
        }];

        if (serviceUUIDIndex == NSNotFound) {
            [peripheral discoverServices:@[myServiceUUIDString]];
            ...

如上例所示,如果系统在你的应用完成发现 service 之前把它终止了,你可以通过调用 discoverServices: 方法从断开的地方重新开始探索恢复的 peripheral 的数据。如果你的应用成功发现了 service ,然后你可以检查是否发现了合适的 characteristic(以及你是否已经订阅了他们)。你的初始化过程经过这样的更新,你就能保证你在正确的时机调用了正确的方法。

@end

上一篇下一篇

猜你喜欢

热点阅读