Android 低功耗蓝牙(Ble) 开发总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 低功耗蓝牙(Ble) 开发总结相关的知识,希望对你有一定的参考价值。
参考技术A android 从 4.3(API Level 18) 开始支持低功耗蓝牙,但是只支持作为中心设备(Central)模式,这就意味着 Android 设备只能主动扫描和链接其他外围设备(Peripheral)。从 Android 5.0(API Level 21) 开始两种模式都支持。低功耗蓝牙开发算是较偏技术,实际开发中坑是比较多的,网上有很多文章介绍使用和经验总结,但是有些问题答案不好找,甚至有些误导人,比如 :获取已经连接的蓝牙,有的是通过反射,一大堆判断,然而并不是对所有手机有用,关于Ble传输速率问题的解决,都是默认Android每次只能发送20个字节,然而也并不是,,,下面进入正文。
这里用的是 Android5.0 新增的扫描API,
这里说一下,如果做蓝牙设备管理页面,可能区分是否是已连接的设备,网上又通过反射或其他挺麻烦的操作,也不见得获取到,官方Api 就有提供
与外围设备交互经常每次发的数据大于 mtu的,需要做分包处理,接收数据也要判断数据的完整性最后才返回原数据做处理,所以一般交互最少包含包长度,和包校验码和原数据。当然也可以加包头,指令还有其他完整性校验。下面分享几个公用方法:
我自己封装的一个BleUtil ,因为涉及跟公司业务关联性太强(主要是传输包的协议不同)就先不开源出来了,如果这边文章对大家有帮助反馈不错,我会考虑上传个demo到github供大家使用,
在这先给大家推荐一个不错 Demo ,里面除了没有分包,协议,和传输速率。基本的功能都有,而且调试数据到打印到界面上了。最主要是它可以用两个个手机一个当中心设备一个当外围设备调试。
首先传输速率优化有两个方向,1 外围设备传输到Android 。2 Android传输到外围设备。
我在开发中首先先使用上面那位仁兄的demo调试,两个Android 设备调试不延时,上一个成功马上下一个,最多一秒发11个20字节的包。
后来和我们的蓝牙设备调试时发现发送特别快,但是数据不完整,他蓝牙模块接收成功了,但是透传数据到芯片处理时发现不完整,我们的硬件小伙伴说因为 波特率 限制(差不多每10字节透传要耗时1ms)和蓝牙模块的buff (打印时是最多100byte,100打印的)限制,就算蓝牙模块每包都告诉你接收成功,也是没透传完就又接收了。后来通过调试每次发20K数据,最后是 Android 发是 20字节/130ms 稳定。给Android 发是 20字节/ 8ms 。 (天杀的20字节,网上都是说20字节最多了)
后来看了国外一家物联网公司总结的 Ble 吞吐量的文章(上面有连接),知道Android 每个延时是可以连续接收6个包的。就改为 120字节/ 16ms (为啥是16ms,不是每次间隔要6个包吗,怎么像间隔两次,这时因为波特率影响,多了5个包100字节,差不多 我们的单片机透传到蓝牙模块要多耗时不到10ms )
而Android 发数据可以申请 我们设备的mtu 来得到最多每次能发多少字节。延时还是130ms,即:241字节/ 130ms 提高12倍,这个速度还可以。
根据蓝牙BLE协议, 物理层physical layer的传输速率是1Mbps,相当于每秒125K字节。事实上,其只是基准传输速率,协议规定BLE不能连续不断地传输数据包,否则就不能称为低功耗蓝牙了。连续传输自然会带来高功耗。所以,蓝牙的最高传输速率并不由物理层的工作频率决定的。
在实际的操作过程中,如果主机连线不断地发送数据包,要么丢包严重要么连接出现异常而断开。
在BLE里面,传输速度受其连接参数所影响。连接参数定义如下:
1)连接间隔。蓝牙基带是跳频工作的,主机和从机会商定多长时间进行跳频连接,连接上才能进行数据传输。这个连接和广播状态和连接状态的连接不是一样的意思。主机在从机广播时进行连接是应用层的主动软件行为。而跳频过程中的连接是蓝牙基带协议的规定,完全由硬件控制,对应用层透明。明显,如果这个连接间隔时间越短,那么传输的速度就增大。连接上传完数据后,蓝牙基带即进入休眠状态,保证低功耗。其是1.25毫秒一个单位。
2)连接延迟。其是为了低功耗考虑,允许从机在跳频过程中不理会主机的跳频指令,继续睡眠一段时间。而主机不能因为从机睡眠而认为其断开连接了。其是1.25毫秒一个单位。明显,这个数值越小,传输速度也高。
蓝牙BLE协议规定连接参数最小是5,即7.25毫秒;而Android手机规定连接参数最小是8,即10毫秒。ios规定是16,即20毫秒。
连接参数完全由主机决定,但从机可以发出更新参数申请,主机可以接受也可以拒绝。android手机一部接受,而ios比较严格,拒绝的概率比较高。
参考:
在iOS和Android上最大化BLE吞吐量
最大化BLE吞吐量第2部分:使用更大的ATT MTU
Android 低功耗蓝牙BLE 开发注意事项
基本概念和问题
1、蓝牙设计范式?
当手机通过扫描低功耗蓝牙设备并连接上后,手机与蓝牙设备构成了客户端-服务端架构。手机通过连接蓝牙设备,可以读取蓝牙设备上的信息。手机就是客户端,蓝牙设备是服务端。
手机做为客户端可以连接多个蓝牙设备,所以手机又可以叫中心设备(Central),蓝牙设备叫外围设备(Peripheral)。
还有另外一个称谓:手机叫主设备(Master),蓝牙设备叫从设备(Slave)。
Android4.3 开始支持低功耗蓝牙,此版本只支持单模式:同时只能工作在中心设备模式或者外围设备模式
Android5.0 开始支持主从一体。换句话说,手机可以扫描并进行连接,连接着蓝牙设备的同时,又可以作为广播者,发送蓝牙广播,等待别的支持蓝牙扫描的设备连接自己。
2、从设备连接数量的问题?
理论层面
从经典蓝牙时代开始,蓝牙有个星型拓扑的概念,一个主设备(Central)外围有七个从设备(Peripheral),蓝牙核心文档规定了:同一时间只允许七个从设备进行连接。
系统层面
Android系统蓝牙协议栈源码中也使用了这个数值,Android手机的蓝牙芯片都是双模蓝牙芯片,即同时支持经典蓝牙和低功耗蓝牙,分析过协议栈源码,建立连接的过程经典蓝牙和低功耗蓝牙是公用的代码,所以手机作为主设备(Central)时,从设备(Peripheral)同时连接的最大值就是7台设备。
实际情况
开发Android客户端以来,遇到的实际情况就是,部分手机(偏低端一些机型,比如采用联发科的解决方案,手机的GPS、蓝牙、Wi-Fi等都是共模的,都集成在一个芯片上)不能达到7台设备。
3、ATT是什么?
ATT是属性协议(Attribute Protocol),定义了客户端与服务器如何相互发送符合标准的消息。
4、GATT是什么?
GATT是通用属性规范(Generic Attribute Profile),定义了如何发现与使用服务、特性与描述符的标准方法。
GATT的规程基本分为:
发现规程:发现服务(Service)、发现特征(Characteristic)等
客户端发起规程:读取特征(readCharacteristic)、写入特征(writeCharacteristic)等
服务端发起规程:比如通知(Notification)和指示(Indicate)
5、低功耗蓝牙频段和信道问题
蓝牙工作在2.45G ISM频段,波段范围是:2400-2483.5 MHz
信道:低功耗蓝牙使用用40个RF信道,这些RF信道中心频率为:f=2402+k*2 MHz, k=0, ... ,39
因为调试指数放宽,低功耗蓝牙的信道与经典蓝牙有所不同。每个信道的功率谱更宽,因此,为了避免邻近信道干扰,低功耗蓝牙的信道宽度为2MHz,而不是经典蓝牙的1MHz
低功耗蓝牙使用的2.45GHz频段已经非常拥挤,仅仅考虑标准的技术就包括:经典蓝牙、低功耗蓝牙、IEEE 802.11、IEEE802.11b、IEEE802.11g、IEEE802.11n以及IEEE 802.15.4。另外,许多私有的无线电同样使用这个频段,包括X10视频中继器、无线报警、键盘和鼠标等。许多其他设备也会在该频段发射噪声,例如街灯和微波炉。
对于2.45G这个频段有个很尴尬的特性:怕水。
举个例子:微波炉的工作原理就是向带有水分的物体发射2.45GHz的微波,利用了水分子能够很好的吸收2.45GHz电磁波,将电磁波能量转换成为自身的热量。也正式这个特性,在很长一段时间里,2.4GHz信道不被人所重视,下雨、雾气甚至是潮湿的墙壁都能吸收无线电波,使传输距离大大衰减。估计这也是全球都对此频段不屑而免费开放的理由之一吧。当人站在两块蓝牙设备中间,并且距离其中一块模块1米左右时,能够检测信号衰减了将近10dB左右!因为人体的70%左右是水分。
是为了尽量避开冲突频段,增加通信的鲁棒性。
6、关于autoConnect参数为true的意义?
在蓝牙核心文档Vol3: Core System Package[Host volume]->Part C: Generic Access Profile的Connection Modes and Procedures章节中有涉及到自动连接建立规程(Auto Connection Establishment Procedure)的定义。
自动连接建立规程用来向多个设备同时发起连接。一个中央设备的主机与多个外围设备绑定,只要它们开始广播,便立刻与其建立连接。跟多细节请参考蓝牙核心文档和协议栈源码。
一些API使用问题
Android 4.3
此版本是首个支持BLE的Android版本,稳定性一般,现在的系统分布情况,基本可以把最低支持版本提高的Android4.4了
Android 5.0
Samsung手机出现BluetoothAdapter.startLeScan()方法使用不当导致的Crash
正常调用过程startLeScan() -> stopLeScan() -> startLeScan() -> stopLeScan(),不会出现Crash
异常调用startLeScan() -> startLeScan()会出现Crash
Android 6.0
在Android 6.0版本,需要APP获取位置权限才可以使用蓝牙API,部分机型在未授权时,调用蓝牙API会引起Crash
Android 6.0.1
Android6.0.1有个连接问题,是系统bug,影响连接问题。
Android 7.0
30s内连续扫描次数不允许大于5次,否则会引起无法扫描到设备的问题,需要重启才可以恢复正常。
并发执行BluetoothGatt.readRemoterssi()会引发DeadObjectException,三星手机出现概率较高。
Android 8.1
扫描方法BluetoothAdapter.startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback);
部分手机如果没有指定serviceuuids值,手机锁屏后,扫描回调会失败,无法扫描到设备。Google亲儿子Pixel系列必现。
待更新......
另外一些普遍问题
BluetoothDevice.getName() 获取名字是有些不可靠的,因为有些情况下获取Name是空,慎用此方法获取的值来作为扫描过滤条件。
BLE设备的建立和断开连接的操作,最好都放在主线程中:例如BluetoothDevice.connectGatt(),BluetoothGatt.connect(),BluetoothGatt.disconnect(),BluetoothGatt.discoverServices()等
BLE 应用异常耗电问题,在连接 BLE 设备的过程中,系统会持有这个 WakeLock,直到连接上或者主动断开连接才会释放。如果BLE设备不在范围内,这个超时时间大约为30s,而这时你可能又要尝试重新连接,这个WakeLock又被重新持有,这样系统就永远不能休眠了。
Android BLE蓝牙的各种问题,只要做到如下几点,大部分问题会得到解决:
原则一:startLeScan()和stopLeScan()一定要确保成对出现、顺序调用。
否则会导致协议栈中mClientIf达到上限,扫描registerClient失败,再也不能扫描到设备,此时onScanFailed会发生errorCode=2。
原则二:BluetoothDevice.connectGatt()、BluetoothGatt.disconnect()和BluetoothGatt.close()一定要顺序调用。一些情况下可以直接越过disconnect()方法直接调用close()。
close()非常重要,对于一个执行过连接方法的设备,不管是否连接成功,最后都要调用close(),让系统底层回收掉资源,否则会有各种问题让你崩溃。
对于连接成功的蓝牙设备,想断开时,可以先调用BluetoothGatt.disconnect(),等待onConnectionStateChange响应断开后,再执行close()。
如果是执行连接方法时出现了无法恢复的错误,比如133、8、19、22、62等,可以直接调用close()。
原则三:读/写特征和描述、设置通知和指示等操作,要确保上一个执行完成了,再执行下一个调用。
Android源码中使用了mDeviceBusy全局变量,同时调用两个API,会导致后调用的直接失败。
附录:API常见错误码
GATT_ERROR 0x85 //133任何不惧名字的错误都出现这个错误码,出现了就认怂吧,重新连接吧。
GATT_CONN_TIMEOUT 0x08 //8 连接超时,大多数情况是设备离开可连接范围,然后手机端连接超时断开返回此错误码。
GATT_CONN_TERMINATE_PEER_USER 0x13 //19 连接被对端设备终止,直白点就是手机去连接外围设备,外围设备任性不让连接执行了断开。
GATT_CONN_TERMINATE_LOCAL_HOST 0x16 //22 连接被本地主机终止,可以解释为手机连接外围设备,但是连接过程中出现一些比如鉴权等问题,无法继续保持连接,主动执行了断开操作。
GATT_CONN_FAIL_ESTABLISH 03E //62 连接建立失败。
为了容易的避坑,基于Android蓝牙接口封装了一个库,将一些会引发错误的地方规避掉了,以后再开发相关应用,可以省一些心力。
以上是关于Android 低功耗蓝牙(Ble) 开发总结的主要内容,如果未能解决你的问题,请参考以下文章
Android BLE低功耗蓝牙开发极简系列(二)之读写操作