android40ble蓝牙
㈠ Android-Ble蓝牙开发Demo示例–扫描,连接,发送和接收数据,分包解包(附源码)
万物互联的物联网时代的已经来临,ble蓝牙开发在其中扮演着举重若轻的角色。最近刚好闲一点,抽时间梳理下这块的知识点。
涉及ble蓝牙通讯的客户端(开启、扫描、连接、发送和接收数据、分包解包)和服务端(初始化广播数据、开始广播、配置Services、Server回调操作)整个环节以及一些常见的问题即踩过的一些坑。
比如
1、在Android不同版本或不同手机的适配问题,扫描不到蓝牙设备
2、如何避免ble蓝牙连接出现133错误?
3、单次写的数据大小有20字节限制,如何发送长数据
蓝牙有传统(经典)蓝牙和低功耗蓝牙BLE(Bluetooth Low Energy)之分,两者的开发的API不一样,本文主讲Ble蓝牙开发,传统蓝牙不展开,有需要的可以自行了解。
相对传统蓝牙,BLE低功耗蓝牙,主要特点是快速搜索,快速连接,超低功耗保持连接和数据传输。
客户端
服务端
Android4.3(API Level 18)开始引入BLE的核心功能并提供了相应的 API。应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。
BLE蓝牙协议是GATT协议, BLE相关类不多, 全都位于android.bluetooth包和android.bluetooth.le包的几个类:
android.bluetooth.
.BluetoothGattService 包含多个Characteristic(属性特征值), 含有唯一的UUID作为标识
.BluetoothGattCharacteristic 包含单个值和多个Descriptor, 含有唯一的UUID作为标识
.BluetoothGattDescriptor 对Characteristic进行描述, 含有唯一的UUID作为标识
.BluetoothGatt 客户端相关
.BluetoothGattCallback 客户端连接回调
.BluetoothGattServer 服务端相关
.BluetoothGattServerCallback 服务端连接回调
android.bluetooth.le.
.AdvertiseCallback 服务端的广播回调
.AdvertiseData 服务端的广播数据
.AdvertiseSettings 服务端的广播设置
.BluetoothLeAdvertiser 服务端的广播
.BluetoothLeScanner 客户端扫描相关(Android5.0新增)
.ScanCallback 客户端扫描回调
.ScanFilter 客户端扫描过滤
.ScanRecord 客户端扫描结果的广播数据
.ScanResult 客户端扫描结果
.ScanSettings 客户端扫描设置
BLE设备分为两种设备: 客户端(也叫主机/中心设备/Central), 服务端(也叫从机/外围设备/peripheral)
客户端的核心类是 BluetoothGatt
服务端的核心类是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE数据的核心类是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
下面详细讲解下客户端和服务端的开发步骤流程
安卓手机涉及蓝牙权限问题,蓝牙开发需要在AndroidManifest.xml文件中添加权限声明:
在搜索设备之前需要询问打开手机蓝牙:
注意: BLE设备地址是动态变化(每隔一段时间都会变化),而经典蓝牙设备是出厂就固定不变了!
通过扫描BLE设备,根据设备名称区分出目标设备targetDevice,下一步实现与目标设备的连接,在连接设备之前要停止搜索蓝牙;停止搜索一般需要一定的时间来完成,最好调用停止搜索函数之后加以100ms的延时,保证系统能够完全停止搜索蓝牙设备。停止搜索之后启动连接过程;
BLE蓝牙的连接方法相对简单只需调用connectGatt方法;
参数说明
与设备建立连接之后与设备通信,整个通信过程都是在BluetoothGattCallback的异步回调函数中完成;
BluetoothGattCallback中主要回调函数如下:
上述几个回调函数是BLE开发中不可缺少的;
当调用targetdDevice.connectGatt(context, false, gattCallback)后系统会主动发起与BLE蓝牙设备的连接,若成功连接到设备将回调onConnectionStateChange方法,其处理过程如下:
判断newState == BluetoothGatt.STATE_CONNECTED表明此时已经成功连接到设备;
mBluetoothGatt.discoverServices();
扫描BLE设备服务是安卓系统中关于BLE蓝牙开发的重要一步,一般在设备连接成功后调用,扫描到设备服务后回调onServicesDiscovered()函数,函数原型如下:
BLE蓝牙开发主要有负责通信的BluetoothGattService完成的。当且称为通信服务。通信服务通过硬件工程师提供的UUID获取。获取方式如下:
具体操作方式如下:
开启监听,即建立与设备的通信的首发数据通道,BLE开发中只有当客户端成功开启监听后才能与服务端收发数据。开启监听的方式如下:
BLE单次写的数据量大小是有限制的, 通常是20字节 ,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
监听成功后通过向 writeCharacteristic写入数据实现与服务端的通信。写入方式如下:
其中:value一般为Hex格式指令,其内容由设备通信的蓝牙通信协议规定;
若写入指令成功则回调BluetoothGattCallback中的onCharacteristicWrite()方法,说明将数据已经发送给下位机;
若发送的数据符合通信协议,则服务端会向客户端回复相应的数据。发送的数据通过回调onCharacteristicChanged()方法获取,其处理方式如下:
通过向服务端发送指令获取服务端的回复数据,即可完成与设备的通信过程;
当与设备完成通信之后之后一定要断开与设备的连接。调用以下方法断开与设备的连接:
源码上传在CSDN上了,有需要的可以借鉴。
=====> Android蓝牙Ble通讯Demo示例源码–扫描,连接,发送和接收数据,分包解包
BLE单次写的数据量大小是有限制的,通常是20字节,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
总体流程如下:
1、定义通讯协议,如下(这里只是个举例,可以根据项目需求扩展)
2、封装通用发送数据接口(拆包)
该接口根据会发送数据内容按最大字节数拆分(一般20字节)放入队列,拆分完后,依次从队列里取出发送
3、封装通用接收数据接口(组包)
该接口根据从接收的数据按协议里的定义解析数据长度判读是否完整包,不是的话把每条消息累加起来
4、解析完整的数据包,进行业务逻辑处理
5、协议还可以引入加密解密,需要注意的选算法参数的时候,加密后的长度最好跟原数据长度一致,这样不会影响拆包组包
一般都是Android版本适配以及不同ROM机型(小米/红米、华为/荣耀等)(EMUI、MIUI、ColorOS等)的权限问题
蓝牙开发中有很多问题,要静下心分析问题,肯定可以解决的,一起加油;
㈡ Android BLE蓝牙踩坑总结
自从 Android-BLE 库开源了一段时间以来,越来越多的小伙伴问到了各种各样的关于BLE的奇怪问题,在这里我想跟大家分享一下本人对于Android BLE蓝牙的一些看法和解决方式,避免刚接触的小伙伴再次踩坑。
很多人曾问过我这个问题,为什么其他手机都没什么问题,就华为的一些手机老是连接不稳定,经常连接的很慢,而且连接上还经常断开。的确,在这里强调一下华为的一部分手机确实很容易出现这种问题,有时候软件、硬件都搞不定,而且经常性收到客户投诉关于华为手机连接稳定性问题,这个的确没有完全解决的办法,只能靠App和硬件的优化,并不是想甩锅给华为,咱也不敢问到底是什么原因,而且我们公司专门针对各个Android版本的手机做过测试,包括蓝牙传输速率的测试,最后发现华为P20的速度竟然跟小米8的速度差了好几倍,按理说P20手机也不便宜啊,为什么手机蓝牙芯片不能做的再好一点呢?
BLE扫描滥用预防
AOSP-BLE扫描滥用说明
息屏状态下,蓝牙扫描日志,因为扫描周期是12s,所以打印的时间戳间隔是12s,这里的日志为系统日志。
https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/include/bt_target.h#1428
stackoverflow问答社区
㈢ Android BLE蓝牙连接异常处理
蓝牙通信过程中异常很常见,大致有以下几种:
1,连接
2,发现服务
3,读写
4,通知
连接失败可能是设备端原因,也可能是手机端原因。不同的手机来自不同的厂家,用的不同的芯片和蓝牙协议栈都会导致蓝牙功能的表现不一致,这都会导致各式各样的兼容性问题,可能有的手机连接成功率高,有的成功率低。设备端原因可能有些时候出现异常导致死机无响应,或某些参数设置得有问题。但对于Android应用层开发来说,能做的很有限,蓝牙通信是在系统服务进程中处理的,我们无法跨进程改变系统的行为,如果是在一个进程我们还可能通过Hook等手段来调整其内在逻辑。另外应用层的接口只是将请求封装传递给系统服务进程,并未做一些实质性的通信,所以应用层虽然是同一个进程的,但是Hook意义也不大。所以我们能做的仅仅是看怎样调整接口的调用,使得整体稳定性更好一点而已。
连接失败分两种,一种是超时,一种是提前返回失败。
关于超时,一般是设备不在周围,或设备断电未发广播,或设备当前被其他人连接。系统默认超时为30s,通常返回133,我们也可以自己设置更短的超时时间,超时则closeGatt,然后重新连接。
关于提前返回失败,一般是有明确的异常,可能是手机蓝牙的异常或者设备异常。
这两种情况建议closeGatt,延时500ms,然后重试。如果重试三次仍然失败,则可以考虑提示用户重启手机蓝牙,或者检查设备是否正常工作。
还有一种情况,连接成功后没过多久连接又断开了,这有可能是设备主动断开,连接成功后有的设备会等待鉴权,如果一定时间内手机端还未发起鉴权则设备端主动断开。也可能连接信道不够稳定导致断开的,此时closeGatt并重新连接即可。
当连接断开时,会收到onConnectionStateChanged回调,这个回调可能会有一定延时,甚至有5s以上。解决的办法是轮询,如每隔1s发起一次读请求,如果连接断了会立即返回失败。
如果蓝牙连接不稳定,可以考虑关掉WIFI,因为WIFI通常和蓝牙共用一个天线。
有的手机上discoverService可能会回调不止一次onServiceDiscover,这个要注意防御。
当连接建立后,可以由设备端发起更改连接间隔,这样能加快后续发现服务以及数据读写的速度。有的手机discover service很慢,原因是connect interval太大了,有的手机会主动向设备发起更改connect interval,而有的手机却不会。这样的话connect interval相差就会很大,实践中发现有的手机是7ms,有的手机是默认的50ms,所以发现service都要8s,甚至20s的都很寻常,这对用户来说是无法忍受的。所以比较好的办法是设备主动发起更改connect interval,而Android系统是没有提供对应API的。
如果发现服务失败,通常来说不用closeGatt,重试一下就好了。如果重试三次还失败,建议清一下缓存,再closeGatt,重新连接。
读写失败要看失败的原因是什么,如果是权限问题,则需要和设备端确认是否开放了相应的读写权限。也可能是要读写的character不存在,可能是设备端修改了固件,手机端需要刷新一下蓝牙缓存,closeGatt再重新连接。如果是其它未知错误,则重试三次,仍然失败则closeGatt。不过通常来说如果是因为连接出了问题导致读写失败的,会收到onConnectionStateChanged回调,此时就不用再无谓的重试了,直接closeGatt,重新连接。
打开/关闭character的notify,必须等收到onDescriptorWrite回调之后才算结束,才能开始下一个任务。
如果打开notify失败,则可以改成周期性轮询的方式去查询character的值。
可参考该文章
Android-BLE-Issues