Best Practices for Interacting w
Core Bluetooth framework 使许多 central 端事务对你的应用透明。即是,你的应用有权控制,同时也负有责任,去实现 central 角色的大多数方面,比如设备发现和连接,以及探索和交互远程 peripheral 上的数据。本章提供了基本原则和最佳实践,帮助你获取负责任的驾驭这一层的掌控能力,特别是在开发iOS应用中。
注意广播使用和电量消耗
Be Mindful of Radio Usage and Power Consumption
在开发一个和BLE设备交互的应用时,请记住BLE通信共享了设备的广播,通过无线的方式传输信号。因为其它形式的无线通信也可能需要使用你设备的广播,比如WIFI、传统蓝牙,甚至其它使用BLE的应用,请将你的应用的无线电使用最小化。
最小化无线电使用对于 iOS 应用开发来说尤其重要,因为无线电使用对 iOS 设备的电池续航有负面影响。下面的指南会帮助你成为一个有素质的无线电使用者。做为回报,你的应用性能会更好,你的设备续航会更久。
仅当你需要扫描设备的时候才去扫描
当你调用 CBCentralManager 类的 scanForPeripheralsWithServices:options: 方法去寻找正在广播的远程 peripheral ,你的 central 设备便使用无线电去监听正在广播的设备,直至你主动停止。
除非你想要寻找更多的设备,否则请在找到一个想要连接的设备后,停止扫描其它设备。用 CBCentralManager
类的 stopScan 方法可以停止扫描其它设备,如这章所示 Connecting to a Peripheral Device After You’ve Discovered It
仅在必要时指定 CBCentralManagerScanOptionAllowDuplicatesKey 选项
远程 peripheral 设备可能一秒会发出多个广播包给正在监听的 central 告诉它们的存在。当你使用 scanForPeripheralsWithServices:options: 方法扫描设备时,默认行为是:一个广播中 peripheral 的多次发现会被合并成一次发现事件,那就是说,每次发现一个新的 peripheral ,central manager 会调用代理对象上的 centralManager:didDiscoverPeripheral:advertisementData:RSSI: 方法,不管它收到了多少广播包。central manager 同时也会调用这个方法如果已发现的 peripheral 广播数据发生变化。
如果你想改变这个默认行为,你可以在调用 scanForPeripheralsWithServices:options: 方法时指定 CBCentralManagerScanOptionAllowDuplicatesKey
常量作为扫描参数。若如此做,central 每次从 peripheral 收到一个广播包就会生成一个事件。关闭默认行为在有些用例中是有用的。例如,基于 peripheral 距离的初始化连接(使用 peripheral 的“接收信号强度指标” Received Signal Strength Indicator, RSSI)。即使如此,请记住指定这个参数会对电池续航和应用性能产生负面影响。因此,仅当需要实现某些特定用例时才指定这个扫描参数。
聪明的探索 peripheral 的数据
当你在开发应用完成某个特定用例时,会发现 peripheral 设备拥有的 service 和 characteristic 可能比你感兴趣的多得多。探索所有的 service 和相关 characteristic 会对电池续航和应用性能造成负面影响。因此,你应该仅仅寻找和探索你需要的 service 和相关 characteristic。
例如,假设你正连接到的 peripheral 有许多可用的 service ,但你的应用只需要访问其中的两个。你可以做到只寻找和发现这两个 service ,通过给 CBPeripheral 类的 discoverServices: 方法传递一个 service UUID(表现为 CBUUID
对象) 的数组,就像这样:
[peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]];
在你发现了这两个你感兴趣的 service 之后,你可以用类似的方法在 service 中寻找和探索你感兴趣的 characteristic 了。再罗嗦一遍,就是简单的给 CBPeripheral 类的 discoverCharacteristics:forService: 方法传递一个代表 characteristic 标识的 UUID 数组,每个 service 都要这样做。
订阅那些变化频繁的 characteristic 值
正如在 Retrieving the Value of a Characteristic 中描述的一样,你有两种方式可以取得一个 characteristic 的值。
- 你可以,每次需要这个值的时候调用 readValueForCharacteristic: 方法显式的拉取
- 你可以,调用一次 setNotifyValue:forCharacteristic: 订阅这个 characteristic 的值,之后当这个值发生变化时你就能收到通知。
最佳实践是,在任何可能的情况下都走订阅的方式,尤其是值经常会发生变化的情况。如何订阅 characteristic 的值,请见 Subscribing to a Characteristic’s Value(第3章6.2节)。
当你已经获得所有需要的数据之后断开设备连接
当不需要一个连接的时候就从 peripheral 设备断开,这样做有助于减少你的应用的无线电使用。以下两种情况下你都应试断开连接:
- 你所订阅的所有 characteristic 值都已经停止发送广播(你可以通过访问 characteristic 的 isNotifying 属性判断它是否还在广播)
- 你已经从 peripheral 上获取了所有需要的数据
以上任一种情况下,请取消任何订阅并从 peripheral 断开连接。你可以通过调用 setNotifyValue:forCharacteristic:
方法取消订阅 characteristic 的值,需要将第一个参数设为 NO。你可以通过调用 CBCentralManager 类的 cancelPeripheralConnection: 方法取消到 peripheral 设备的连接。
[myCentralManager cancelPeripheralConnection:peripheral];
注意:cancelPeripheralConnection: 方法是非阻塞的,所以若你请求断开时,peripheral 上还有挂起的任何 CBPeripheral 类的命令,它们可能会完成执行,也可能不会。因为其它应用也可能连接着这个 peripheral,取消一个本地连接也不能保证底下的物理层连接能立即断开。然而从应用的视角来说,这个 peripheral 被认为已经断开,且 central manager 对象在它的 delegate 对象上调用 centralManager:didDisconnectPeripheral:error: 方法。
重新连接到 Peripheral
使用 Core Bluetooth framework 你可以通过三种方法重新连接到 peripheral。
- 使用 retrievePeripheralsWithIdentifiers: 方法取回一个已知 peripheral 的列表,即过去曾经发现过或连接过的 peripheral 。如果你正在找的 peripheral 在这个列表中,就可以尝试连接它。这种重连方式在 Retrieving a List of Known Peripherals (下文即是) 有详细描述。
- 使用 retrieveConnectedPeripheralsWithServices: 方法取得当前正连接到系统的 peripheral 的列表。如果你在找的 peripheral 在这个列表中,就可以在应用本地建立连接关系。这种重连方式在 Retrieving a List of Connected Peripherals (下文即是)有详细描述。
- 使用 scanForPeripheralsWithServices:options: 方法扫描并发现 peripheral。如果能找到,就连接它。这种方式定义在 Discovering Peripheral Devices That Are Advertising (第2章) 和 Connecting to a Peripheral Device After You’ve Discovered It (第2章)。
取决于具体用例,你可能并不希望每次重新连接时都要对同一个 peripheral 做一遍扫描、发现的流程。而是希望通过另两种方式先尝试连接。如图5-1所示,一种可能的重连工作流正如上文所列的顺序那样依次尝试这三种方式。
图5-1 一种重连工作流的示例
注意:你决定尝试几种重连方式,以及按什么顺序来做,会随着你的应用想要实现什么样的用例而变化。例如,你可能决定直接放弃第一种重连方式,或是,你可能决定并行的发起前两种方式。
取回一个已知 peripheral 列表
当你第一次发现一个 peripheral 时,系统会生成一个标识符(一个 UUID, 表现为一个 NSUUID 对象)来标识这个 peripheral。于是你可以保存这个标识符(比如用NSUserDefaults
),后续就可以用它尝试重连连接这个 peripheral,方法是调用 CBCentralManager 类的 retrievePeripheralsWithIdentifiers: 方法。
当你的应用启动时,调用 retrievePeripheralsWithIdentifiers: 方法,传入一个包含 peripheral 标识符的数组,这些 peripheral 是之前发现且连接过,并把他们的标识符保存下来。如下:
knownPeripherals =
[myCentralManager retrievePeripheralsWithIdentifiers:savedIdentifiers];
central manager 会尝试匹配你传入的标识符,返回结果为一个 CBPeripheral 对象数组。如果没有发现匹配,返回结果数组为空,你需要尝试另两种重连方式。如果返回数组不为空,让用户选择(在UI中)要尝试重连哪个 peripheral。
当用户选择了一个 peripheral ,调用 CBCentralManager 类的 connectPeripheral:options:
方法尝试重连。如果这个 peripheral 设备可以被连上,central manager 会调用它的 delegate 对象的 centralManager:didConnectPeripheral: 方法,这个 peripheral 设备即被成功连接了。
注意:可能有多种原因会导致 peripheral 设备连接不上。比如,设备可能不在 central 附近。还有,部分 BLE 设备用了随机设备地址,周期性的变化。因此,即使设备就在附近,它的地址可能跟上次被系统发现时不一样了,这种情况下,你尝试连接的 CBPeripheral 对象与实际 peripheral 设备不一致。如果因为 peripheral 地址变化导致的无法重连,你必须使用 scanForPeripheralsWithServices:options: 方法重新发现它。
关于随机设备地址的更多信息,请看 Bluetooth 4.0 specification, Volume 3, Part C, Section 10.8 以及 Bluetooth Accessory Design Guidelines for Apple Products
取回已连接 peripheral 列表
另一种重新连接到 peripheral 的方法是检查你正在寻找的 peripheral 是否已经和系统连接了。(比如,被另一个应用)你可以通过调用 CBCentralManager 类的 retrieveConnectedPeripheralsWithServices: 方法来这样做,这个方法会返回一个 CBPeripheral 对象的数组,代表了当前正和系统连接的 peripheral 列表。
因为当前可能有不止一个 peripheral 连接到了系统,你可传入一个 CBUUID 对象(代表了 service 的 UUID)来取回当前和系统连接着并且包含了你指定的 UUID 的 service 的 peripheral。如果当前没有 peripheral 设备连接到系统,返回数组就为空,你应该试试另外两种重连方式之一。如果数组不为空,让用户选择(在UI中)尝试重连哪一个。
假设用户找到并且选择了所须的 peripheral,请通过调用 CBCentralManager 类的 connectPeripheral:options: 方法在本地将它连接到你的应用。(即使设备已经连接到系统,你仍然必须在本地把它连接到你的应用,之后才可以开始探索和与之交互。)当本地连接建立之后,central manager 会调用它的 delegate 对象的 centralManager:didConnectPeripheral:,peripheral 才算成功重连上。