一、后台扫描
手机作为中心模式(client):
打开后台模式中的使用蓝牙功能 TARGET→Capabilities→Background Modes→Uses Bluetooth LE accessories(勾选)
二、扫描设备方法
centralManager为蓝牙中心模块
// centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
centralManager.scanForPeripherals(withServices: nil, options:nil)
三、ble扫描流程
从调用iOS的scan接口,到收到系统回调,这中间其实有下面几步的交互:
- BLE设备端发送广播ADV_IND
- iOS手机端收到广播后,向该BLE设备发送SCAN_REQ
- BLE设备端收到SCAN_REQ后,发送广播SCAN_RSP
- iOS将ADV_IND和SCAN_RSP合包后,通过系统回调通知调用者
按照蓝牙协议,这几个步骤是分开的,只不过iOS底层帮我们合并了而已。
四、考虑几个问题
- iOS收到设备端发出的ADV_IND之后,是不知道还有没有更多数据的,那么SCAN_REQ的发送机制是怎样的呢?
- iOS发出SCAN_REQ后,并不知道会不会有SCAN_RSP,那么是怎样处理的?
- ADV_IND和SCAN_RSP数据是怎样合并的?
4.1、经过自己的测试和苹果官方技术支持(code-level support)的沟通,初步有以下结论:
- 如果App在前台,iOS会处于主动搜索模式,在此情况下,iOS会尽可能的在收到ADV_IND后发送SCAN_REQ,这里说尽可能的原因,是因为按照苹果官方技术支持的说法,iOS会发送SCAN_REQ,但根据自己抓包显示,有时候iOS会收到几包ADV_IND后才会发送SCAN_REQ
- 如果App进入后台,iOS会进入被动搜索模式,此时,iOS会缓存SCAN_RSP,并会降低SCAN_REQ的发送频率,即可能几包ADV_IND后才会发送一包SCAN_REQ,此时iOS会将收到的ADV_IND和缓存的SCAN_RSP合包告诉调用者。
- 根据实际测试结果,发现App在前台时也会有使用SCAN_RSP缓存的情况。
从而推测,iOS将ADV_IND和SCAN_RSP合包的机制为:
- iOS收到ADV_IND
- iOS发送SCAN_REQ
- iOS从缓存中查找SCAN_RSP
- 如果有缓存,则将缓存的SCAN_RSP和ADV_IND合并通知App
- 如果没有缓存,则直接将ADV_IND通知App
- SCAN_RSP的缓存是有时间的,具体时间未知
4.2、由此,会产生一个问题:
当设备的广播数据发生变化(ADV_IND和SCAN_RSP都产生变化),iOS可能会上报一个新的ADV_IND数据和老的SCAN_RSP数据。造成数据错乱。但当SCAN_RSP更新后,数据就可恢复正常,通常收到1-3包ADV_IND数据后可恢复正常
如:
BLE设备一直在广播
SCAN_RSP = 3f
ADV_IND = 3f
iOS此时向App广播的数据也是
SCAN_RSP = 3f
ADV_IND = 3f
此时,BLE设备广播发生变化,广播数据变为
SCAN_RSP = 42
ADV_IND = 42
App在收到广播包变化的第一个系统回调通常为
SCAN_RSP = 3f //老的SCAN_RSP
ADV_IND = 42 //新的ADV_IND
然后,App通常会在收到1-3个系统回调后变为正常,这个取决于SCAN_RSP的更新时间,所以具体什么时候数据能恢复正常是不确定的,1-3包只是经验值。
所以,如果你的BLE设备的广播数据会发生变化,那么是需要考虑数据错乱问题的。可以对ADV_IND+SCAN_RSP增加校验,如果是错的数据就丢掉。
五、遇到的坑
- 坑一:由于苹果的限制,使用第一种方式扫描,APP在后台运行时是扫描不到任何信息的;如果想在后台扫描蓝牙设备,必须使用第二种方式;
- 坑二:使用第二种方式需要注意,如果设置
CBCentralManagerScanOptionAllowDuplicatesKey的值为NO
,在后台调用扫描时只能,扫描到一次,即使蓝牙广播的数据有变化,也不会接收到新的广播 - 坑三:使用第二种方式,即使
CBCentralManagerScanOptionAllowDuplicatesKey如果设置为YES
,会持续接收到蓝牙发出的广播,但是接收到的蓝牙广播的内容是不会变的;(这里苹果是不推荐我们设置为YES,因为这对手机的电量消耗等是有影响的,但是在某些特定的场景下我们是必须这样做的) - 坑四:即使我们使用第二种方式扫描,也设置了
CBCentralManagerScanOptionAllowDuplicatesKey为YES
,但是如果超过三分钟扫描不到任何蓝牙设备,后台任务一样会停止。
六、建议
由于苹果的这种特性,建议在前台时扫描蓝牙设备时,设置CBCentralManagerScanOptionAllowDuplicatesKey为NO
;在后台扫描蓝牙时,设置CBCentralManagerScanOptionAllowDuplicatesKey为YES