Android蓝牙开发入坑指南

2,707 阅读9分钟
原文链接: www.jianshu.com

BLE简述

蓝牙是一套非常庞大复杂的协议栈,通俗的说就是一组应用于无线系统通信的约定,各个厂家根据这个约定生产出了各种蓝牙设备。由于蓝牙协议栈非常的庞大,很多厂商并不会完全实现蓝牙协议栈的所有功能。Android 4.3以后蓝牙开发一般都是基于低功耗蓝牙BLE4.0,BLE协议是蓝牙协议的子集。

BLE协议结构

上图是BLE协议结构,还是挺复杂的,Android开发人员可能只对GATT这个词有点印象,Android Framework帮我们屏蔽了上述大部分协议细节。Android的bluetooth包提供了BluetoothGattCallback类用来处理蓝牙连接,大致的过程是:

  1. 扫描蓝牙
  2. connectGatt发起连接
  3. 连接成功,扫描服务(Service)
  4. 读取属性(Characteristic)和描述信息(Description)
    具体的程序实现网上教程很多,这里不作介绍了,Service、Attribute、Description之间的关系见下图:
Service、Attribute、Description关系

蓝牙模块理论上说可以有许多Profile,不过大多数蓝牙模块就一个;Profile也可以包含很多Service(一般就一个),Service可以包含很多Characteristic(一般不止一个),有的蓝牙包含一个读Characteristic,一个写Characteristic,有的蓝牙读写共用一个Characteristic,Characteristic内部包含一个字节数组Value[],对于读写Characteristic来说Value[]就是用于收发的数据。Android Ble sdk调用比较麻烦,回调太多了,主要原因就在这里,蓝牙协议层级实在太多了。

BLE开发注意点

Android BLE开发过程中遇到的许多问题,严格来说并不都是Android本身的问题,很多问题来自作为通信目标的蓝牙模块,这些问题都需要开发人员注意:

信号干扰

蓝牙和WIFI都具有在2.4GHz信道通信的能力,某些Android设备蓝牙与Wifi信号会冲突,干扰特别严重,具体表现为蓝牙连接成功后收发数据时丢包和超时。这个问题如果出现了,几乎没有什么解决方案,只能在上层应用做好容错处理。

蓝牙初始化崩溃

这个问题存在于Android4.*的设备,蓝牙扫描设备后会往本地保存扫描记录,但是这个记录有上限,大概是500条,也就是说有搜索过500个不同的蓝牙设备后,手机再调用蓝牙扫描就会崩溃,系统设置里蓝牙扫描也会崩。这个问题影响不大,因为正常用户很难遇到,而且升级到Android 5.0以上就不会出现了。

连接间隔

基本上绝大多数Android蓝牙问题都是因为连接间隔导致的,那么连接间隔是什么呢?

蓝牙通信是基于电磁波的全双工通信,蓝牙通信的双方实际就是在不停的发出广播信号。广播消息意味着所有的设备都能收到消息,那么怎么确认每个消息是谁发给谁的呢?所以蓝牙通信前要先建立蓝牙连接,建立蓝牙连接简单理解的话,其实就是双方协商一致,以同一的步调收发数据,这个收发信号的间隔时间称为连接间隔。IOS蓝牙的连接间隔是20ms,直观的感受就是苹果蓝牙传输比较快;而Android蓝牙的连接时隔各厂商都不一样,有40ms、50ms、70ms等等,都比IOS要大一些。还有非常其怪的:华为2018年以后生产的很多手机型号,正常蓝牙通信时是40ms,但如果同时手机正在进行视频聊天,蓝牙连接间隔就会立即增加到70ms。

蓝牙模块每个连接间隔内收发的数据包有效载荷最大是20字节,写数据时如果往Characteristic里写数据20个字节,多余的数据会丢弃。假设连接间隔是40ms,那么蓝牙模块传输速率就是1000/40*20=500B/s,实在太慢了,BLE协议不适合收发大量数据。

连接间隔本来是一个非常底层的通信概念,Android开发时无法修改连接间隔。按理说开发APP应该不用关心连接间隔才对,就好像往磁盘上保存文件不用关心磁盘的转速一样,但是通信出错排查问题排查到这儿了,我们又不能不重视,这就有点像路由器的MTU一样,有时还真会导致服务器BUG。

连接间隔导致的问题一般都是效率问题,收发数据量太大,数据还没传完就超时了,连接间隔是系统控制的,无法修改,所以这个问题没有什么解决方案,只能在软件设计上做好容错处理,给数据传输留足时间。

连接间隔还会导致一些其怪的丢包问题,这种问题很难排查,好在有蓝牙协议分析仪可以去监听蓝牙信号,不过监听出来了,也没什么解决办法,如果是自研硬件的话,只能在硬件上下点功夫,Android上没有可修改的余地。

BLE与GPS

严格来说BLE与GPS真是一点关系都没有,但是Android很出人意料地将这俩联系在了一起,扫描蓝牙设备必须要申请定位权限!硬说的话,可能是因为蓝牙扫描能够获取信号强度,有了信号强度就可以推算距离,间接的就实现了定位吧!Android Framework其实也提供了不需要定位权限的扫描方法,其实就是非BLE的普通蓝牙扫描,但是这样无法获取scanRecord,也就是说无法得到BLE硬件的service uuid以及广播出来的数据,只能得到目标设备名字,几乎没法用。

另外,申请定位权限了并不表示就一定能扫描蓝牙了,有的Android设备必须要打开GPS定位的开关,否则就是扫描不出来,让人摸不着头脑。有时候Logcat里都系统日志已经打出来蓝牙扫描记录了,但是就是不触发LeScanCallback的回调,这种现象经常出现在vivo手机上。

配对问题

蓝牙协议提供了配对机制,其实就是让蓝牙记住目标设备,下次连接免去了扫描的步骤。其实扫描和配对都不是必须的,只要知道蓝牙设备的MAC地址,就可以直接发起蓝牙连接了(IOS有所差别,只能得到UUID,无法得到MAC地址)。但可能是出于安全考虑,配对之后的设备,蓝牙建立连接时会发起加密申请,询问目标设备是否支持加密通信,哪果目标设备不支持加密又不知道怎么应答,那连接就挂掉了,自研蓝牙设备时需要注意一下。

蓝牙开关问题

Android可以通过程序开启、关闭蓝牙,但是开启蓝牙需要动态权限申请,这倒是小事,交互设计时给个对话框和用户说一下就好了。开启蓝牙的操作即时返回结果的,而开启蓝牙是需要时间的,异步处理,大概几十毫秒,所以enable()后立即扫描蓝牙往往什么也搜不到。这个注意一下就好了,代码里加个延时不费事。一个特别要注意的事情,蓝牙enable()和disable()切记不能频繁操作,否则手机蓝牙会出现一现难琢磨的现象,甚至会被搞死机…而且实践发现,代码里enable()和在蓝牙设置里手动开启蓝牙是不一样的,有的时候enable()后蓝牙死活连不上,但是在系统设置里重新关闭、开启蓝牙就正常了。总之enable()要谨慎使用,有的手机(三星)特娇贵,蓝牙死机了重启手机都不好使。

Android与IOS对比

IOS蓝牙比Android蓝牙要稳定的多,毕竟型号单一嘛。在开发层面,IOS蓝牙不会显示设备MAC地址,其实对用户来说一般不会有查看蓝牙MAC地址的需求。蓝牙性能上,除了传输效率高,IOS蓝牙建立连接也比Android顺畅得多,尤其是蓝牙模块Characteristic很多的时候Android连接时discoverServices()会很慢。

企业采购蓝牙模块千万不能只用iphone做验证,IOS蓝牙实在比Android好太多。IOS上调试蓝牙可以用非常著名的LightBlue,体验非常棒;相应的Android上可以用nRF-Connect,体验差点儿,不过该有的功能都有的。

总结

个人实践来看,Android蓝牙模块稳定性并不高,和4G、wifi模块相比差很远,这其实是用户需求决定的,手机蓝牙确实很少使用,因而手机厂商对蓝牙模块的重视程度也不高,我们多次发现即使同一型号的手机,蓝牙性能也会天差地别,有的连接很稳定,有的丢包特别严重。我们猜测可能手机代工不一样,或者蓝牙模块不是采购自同一供应商。

蓝牙协议之所以被很多厂商所接受,就是因其功能很完善,实现灵活性很高,蓝牙模块只要实现蓝牙协议规定的功能即可,而代码如何写这个就是厂商自己的事情了,所以肯定会有兼容性能问题,兼容性问题总结下来就三个:扫描不到、连接不上、收不到数据,企业采购蓝牙模块一定要做好验收工作。

Anroid蓝牙给开发者的印象多是“不靠谱”,但是用于小规模、短时间数据传输还是非常方便的,比如用于共享单车开锁、智能手环等等。如果需要大规模数据传输的话还是wlan比较靠谱,比如智能摄像头、无人机都是采用wlan传输数据,有的智能硬件会把蓝牙与wifi模块相结合,比如通过蓝牙设置wlan密码接入wifi网络。蓝牙具有的功耗低、体积小的特点,这是很多无线通信方式无法批拟的,因此蓝牙在物联网领域还是大有可为的,开发者如何把充分利用手机蓝牙,我们还需要不断探索。