转载

【iOS】CoreBluetooth3 作为 Central 时的数据读写(最佳实践)

之前介绍了比较多的基础内容吧,本文会结合 API 中的最佳实践和自己在开发中遇到的问题来谈谈需要注意的地方。

当然,文本中的设备依然是作为 Central 的。

如果只看这篇文章有明白的话,可以先参考前几篇文章:

CoreBluetooth1 初识

CoreBluetooth2 作为 Central 时的数据读写

CoreBluetooth3 作为 Central 时的数据读写(补充)

在设备上一般都有很多地方要用到无线电通信,Wi-Fi、传统的蓝牙、以及使用 BLE 通信的 App 等等。这些服务都是很耗资源的,尤其是在 iOS 设备上。所以本文会讲解到如何正确的使用 BLE 以达到节能的效果。

只扫描你需要的 Peripheral

在调用  CBCentralManager 的  scanForPeripheralsWithServices:options: 方法时,Central 会打开无线电去监听正在广播的 Peripheral,并且这一过程不会自动超时。(所以需要我们手动设置 Timer 去停掉,文后会提到)

如果只需要连接一个 Peripheral,那应该在  centralManager:didConnectPeripheral: 的回调中,用  stopScan  方法停止搜索。

只在必要的时候设置 CBCentralManagerScanOptionAllowDuplicatesKey

Peripheral 每秒都在发送大量的数据包, scanForPeripheralsWithServices:options: 方法会将同一 Peripheral 发出的多个数据包合并为一个事件,然后每找到一个 Peripheral  都会调用  centralManager:didDiscoverPeripheral:advertisementData:RSSI: 方法。另外,当已发现的 Peripheral  发送的数据包有变化时,这个代理方法同样会调用。

以上合并事件的操作是  scanForPeripheralsWithServices:options: 的默认行为,即未设置  option 参数。如果不想要默认行为,可将  option 设置为  CBCentralManagerScanOptionAllowDuplicatesKey 。设置以后,每收到广播,就会调用上面的回调(无论广播数据是否一样)。关闭默认行为一般用于以下场景:根据 Peripheral 的距离来初始化连接(距离可用信号强度 RSSI 来判断)。设置这个  option 会对电池寿命和 app 的性能产生不利影响,所以一定要在必要的时候,再对其进行设置。

正确的搜索 Service 与 Characteristic

在  CoreBluetooth2 作为 Central 时的数据读写 中也提到过这个问题,在搜索过程中,并不是所有的 Service 和 Characteristic 都是我们需要的,如果全部搜索,依然会造成不必要的资源浪费。

假设你只需要用到 Peripheral 提供的众多 Service 的两个,那么在搜索 Service 的时候可以设置要搜索的 Service 的 UUID(打包为  CBUUID ,关于  CBUUID 的介绍可见  CoreBluetooth3 作为 Central 时的数据读写(补充) )。

[peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]]; 

用这种方式搜索到 Service 以后,也可以用类似的办法来限制 Characteristic 的搜索范围( discoverCharacteristics:forService: )。

接收 Characteristic 数据

接收 Characteristic 数据的方式有两种:

  • 在需要接收数据的时候,调用  readValueForCharacteristic: ,这种是需要主动去接收的。
  • 用  setNotifyValue:forCharacteristic:  方法订阅,当有数据发送时,可以直接在回调中接收。

如果 Characteristic 的数据经常变化,那么采用订阅的方式更好。

适时断开连接

在不用和 Peripheral 通信的时候,应当将连接断开,这也对节能有好处。在以下两种情况下,连接应该被断开:

  • 当 Characteristic 不再发送数据时。(可以通过  isNotifying  属性来判断)
  • 你已经接收到了你所需要的所有数据时。

以上两种情况,都需要先结束订阅,然后断开连接。

[peripheral setNotifyValue:NO forCharacteristic:characteristic]; [myCentralManager cancelPeripheralConnection:peripheral]; 

注意: cancelPeripheralConnection: 是非阻塞性的,如果在 Peripheral 挂起的状态去尝试断开连接,那么这个断开操作可能执行,也可能不会。因为可能还有其他的 Central 连着它,所以取消连接并不代表底层连接也断开。从 app 的层面来讲,在 Peripheral 决定断开的时候,会调用  CBCentralManagerDelegate 的  centralManager:didDisconnectPeripheral:error: 方法。

再次连接 Peripheral

Core Bluetooth 提供了三种再次连接 Peripheral 的方式:

  • 调用  retrievePeripheralsWithIdentifiers:  方法,重连已知的 Peripheral 列表中的 Peripheral(以前发现的,或者以前连接过的)。
  • 调用  retrieveConnectedPeripheralsWithServices:  方法,重新连接当前【系统】已经连接的 Peripheral。
  • 调用  scanForPeripheralsWithServices:options:  方法,连接搜索到的 Peripheral。

是否需要重新连接以前连接过的 Peripheral 要取决于你的需求,下图展示了当你尝试重连时可以选择的流程:

【iOS】CoreBluetooth3 作为 Central 时的数据读写(最佳实践)

三列代表着三种重连的方式。当然这也是你可以选择进行实现的,这三种方式也并不是都需要去实现,依然取决于你的需求。

尝试连接已知的 Peripheral

在第一次成功连上 Peripheral 之后,iOS 设备会自动给 Peripheral 生成一个 identifier ( NSUUID 类型),这个标识符可通过  peripheral.identifier 来访问。这个属性由  CBPeriperal 的父类  CBPeer 提供,API 注释写着:

The unique, persistent identifier associated with the peer.

因为 iOS 拿不到 Peripheral 的 MAC 地址,所以无法唯一标识每个硬件设备,根据这个注释来看,应该 Apple 更希望你使用这个  identifer 而不是 MAC 地址。值得注意的是,不同的 iOS 连接同一个 Peripheral 获得的  identifier 是不一样的。所以如果一定要获得唯一的 MAC 地址,可以和硬件工程师协商,让 Peripheral 返给你。具体的场景我在上一篇文章 中介绍过。

那么继续回到重新连接 Peripheral 这个话题上来。当第一次连接上 Peripheral 并且系统自动生成 identifier 之后,我们需要将它存下来(可以使用  NSUserDefaults )。在再次连接的时候,将 identifiers 读出来,调用  retrievePeripheralsWithIdentifiers: 方法即可。

knownPeripherals = [myCentralManager retrievePeripheralsWithIdentifiers:savedIdentifiers]; 

调用这个方法之后,会返回一个  CBPeripheral 的数组,包含了以前连过的 Peripheral。如果这个数组为空,则说明没找到,那么你需要去尝试另外两种重连方式。如果这个数组有多个值,那么你应该提供一个界面让用户去选择。

如果用户选择了一个,那么可以调用  connectPeripheral:options:  方法来进行连接,连接成功之后依然会走  centralManager:didConnectPeripheral:  回调。

注意,连接失败通常有一下几个原因:

  • Peripheral 与 Central 的距离超出了连接范围。
  • 有一些 BLE 设备的地址是周期性变化的。所以,即使 Peripheral 就在旁边,如果它的地址已经变化,而你记录的地址已经变化了,那么也是连接不上的。如果是因为这种原因连接不上,那你需要调用  scanForPeripheralsWithServices:options:  方法来进行重新搜索。

更多关于随机地址的资料可以看  《苹果产品的蓝牙附件设计指南》

连接系统已经连接过的 Peripheral

另外一种重连的方式是通过检测当前系统是否已经连上了需要的 Peripheral (可能被其他 app 连接了)。调用  retrieveConnectedPeripheralsWithServices: 会返回一个  CBPeripheral 的数组。

因为当前可能不止一个 Peripheral 连上的,所以你可以通过传入一个 Service 的  CBUUID  的数组来过滤掉一些不需要的 Peripheral 。同样,这个数组有可能为空,也有可能不为空,处理方式和上一节的方式相同。找到要连接的 Peripheral 之后,处理方式也和上一节相同。

自动连接

可以再程序启动或者需要使用蓝牙的时候,判断是否需要自动连接。如果需要,则可以尝试连接已知的 Peripheral。这个重连上一个小节刚好提到过:在上一次连接成功后,记录 Peripheral 的 identifier,然后重连的时候,读取即可。

连接超时

因为 Core Bluetooth 并未帮我们处理连接超时相关的操作,所以超时的判断还需要自己维护一个Timer。可以在 start scan 的时候启动(注意如果是自动连接,那么重连的时候也需要启动),然后在搜索到以后 stop timer。当然,如果超时,则看你具体的处理方式了,可以选择 stop scan,然后让用户手动刷新。

原文  http://www.saitjr.com/ios/core-bluetooth-read-write-as-central-role-best-practices.html
正文到此结束
Loading...