一年多了,没有来简书写文章,今天恰逢风高气爽,总结下个人对BLE蓝牙一些问题
什么是经典蓝牙,什么是BLE蓝牙,什么是双模蓝牙
A、经典蓝牙:
1)、传声音:如蓝牙耳机、蓝牙音箱。蓝牙设计的时候就是为了传声音的,所以是近距离的音频传输的不二选择。现在也有基于WIFI的音频传输方案,例如Airplay等,但是WIFI功耗比蓝牙大很多,设备无法做到便携。因此固定的音响有WIFI的,移动的如耳机、便携音箱清一色都是基于经典蓝牙协议的。
2)、传大量数据: 例如某些工控场景,使用Android或Linux主控,外挂蓝牙遥控设备的,可以使用经典蓝牙里的SPP协议,当作一个无线串口使用。速度比BLE传输快多了。
B、BLE蓝牙:
1)、耗电低,数据量小,如遥控类(鼠标、键盘),传感设备(心跳带、血压计、温度传感器、共享单车锁、智能锁、防丢器、室内定位)。
2)、目前手机和智能硬件通信的性价比最高的手段,直线距离约50米,一节5号电池能用一年,传输模组成本便宜,远比WIFI、4G等大数据量的通信协议更实用。虽然蓝牙距离近了点,但胜在直连手机,价格超便宜。以室内定位为例,商场每家门店挂个蓝牙beacon,就可以对手机做到精度10米级的室内定位,将来的蓝牙5.1更可以实现厘米级室内定位。
C、双模蓝牙:
1)、智能电视遥控器:很多智能电视配的遥控器带有语音识别,需要用经典蓝牙才能传输声音。而如果做复杂的按键,例如原本键盘表上没有的功能,经典蓝牙的HID按键协议就不行了,得用BLE做私有协议。
2)、降噪耳机:很多降噪耳机上通过APP来调节降噪效果,也是通过BLE来实现的私有通信协议。
开发过程中遇到的问题
1、已配对的列表消失问题
BluetoothAdapter.getDefaultAdapter().getBondedDevices();
此方法是获取设备已经pair过的列表,有时候没有去removeBond
,明明有一个设备,但是某种情况下消失,也就是getBondedDevices().size=0
目前倾向于是系统做了一些操作
2、在扫描BLE设备的时候,有几率会发现由于BLE地扫描导致地整个App页面卡
3、进行BLE的基本操作导致BLE Crash问题
4、关于不断连接BLE的时候,底层报错status =133的问题
- 复现方法:不断地disconnect 和 connect
- 补充说明:连接BLE,假如BLE已经connect到另外的设备,这个时候status 133,是正常现象,但是我们的平板发生的概率很大
5、在蓝牙已经打开的状态下,开启蓝牙扫描的时候,getBluetoothLeScanner()==null
,然后设备不能开启扫描
6、蓝牙的建立拦截的过程中,发生无响应的状态
- 蓝牙连接过程的说明:开始BLE扫描-->获取到目标设备-->开始pair-->成功--->开始连接-->连接成功--->changeMTU-->onMtuChanged() 这个是GattCallback的内置方法,回调--->接着开始 gat.discoverServices();--->秘钥协商--->真正的连接状态
- 发现问题:当手动调用 changeMTU 后,onMtuChanged方法不回调,就卡在中间
开发中优化的建议
- 1、蓝牙扫描设备
我实现的方式是通过开启一个线程池,这个是
Executors.newSingleThreadExecutor()
,
/**
* How to understand:
* 1. A new task will be placed in corePool, the core thread pool, the size is 1, where corePoolSize=maximumPoolSize=1
* 2. When the core thread pool is full, it will be placed on the LinkedBlockingQueue. If the LinkedBlockingQueue is a bounded queue, a bounded blocking queue composed of a linked list structure, and it is also full. size=Integer.MAX_VALUE
* 3. Create a new task in the thread pool smaller than the maximum until the maximum thread pool is full
* 4. It will go to the rejection queue to see what kind of rejection plan it is, and then just like that
* 5. Because defaultHandler = new AbortPolicy(); it is possible to throw an exception, although this chance is small
* 6. The execution time of the execute method is usually a slow process. If it is a fast process, you will not be placed in the thread pool, so I think this is suitable for download initialization
*/
/**
* 如何理解:
* 1.会在corePool,核心线程池中放置一个新任务,大小为1,其中corePoolSize=maximumPoolSize=1
* 2.当核心线程池满时,会放到LinkedBlockingQueue上。 如果LinkedBlockingQueue是一个有界队列,一个由链表结构组成的有界阻塞队列,它也是满的。 大小=整数.MAX_VALUE
* 3.在线程池中创建一个小于最大值的新任务,直到最大值线程池满
* 4.它会去拒绝队列看它是什么拒绝计划,然后就这样
* 5. 因为 defaultHandler = new AbortPolicy(); 有可能抛出异常,虽然这个机会很小
* 6.execute方法的执行时间通常是一个缓慢的过程。 如果是快进程,你就不会被放到线程池中,所以我觉得这个适合下载初始化
*/
复制代码
-
2、在实际过程中,BLE发生过假连接的情况,所以判断BLE是否是在连接的状态很关键 主要是通过 反射去获取连接状态
Method method = bluetoothDeviceClass.getDeclaredMethod("isConnected", (Class[]) null);
-
3、 BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner() 在BLE关闭的状态下,这个会null,但是我在开发过程中其实也发现了,也会为null,所以需要判断null,在扫描设备的时候也要主要判断,要不然,整个子线程crash了,都没有反应
-
4、关于一直扫描Pair的问题,由于安卓系统各不相同,所有在某些板子上有可能发生一直Pair的情况, 个人建议如果Pair失败的次数过多的情况下,可以偷偷的打开和关闭BLE即可解决问题
-
5、蓝牙的建立拦截的过程中,发生无响应的状态 可以偷偷的打开和关闭BLE即可解决问题,具体原因我大约知道,但是不敢肯定,所以不贴原因了
-
6、关于判断是否BLE连接上了,我看过好多开源的工程都在这个方法中判断onConnectionStateChange 但是其实不太好,说下我的理由,在这里判断连接失败是可以的,但是呢?连接成功呢,如果项目不涉及到秘钥协商的东西,那应该可以,但是 ,我从事过的商业项目都是加密的点对点的加密,所以当一个完善的项目应该是秘钥协商之后
-
流程 onConnectionStateChange--> changeMTU(int) --->onMtuChanged()--> gatt.discoverServices()-->onCharacteristicRead()--->根据最后读取的特征值去判断,因为商业项目有可能会读取多个硬件信息,版本号,等等,连接成功,可以正常通讯
-
7、gatt 不用了需要close,而不是disconnet
-
8、如果你正在实现一个BLE模块,建议使用状态模式去实现代码,但是也特备要注意下状态管理不能够乱,如果乱了,状态模式反而比较麻烦
-
9、重连通过 connectByName Or connectByAddress 这种方法去获取到的BluetoothDevice其实地址是同一个,所以直接使用BluetoothDevice去connect即可,不要花花肠子那么多
-
10、一般情况下,BLE设备有恢复出厂设置,假如这个时候使用虚拟mac地址的,代码需要更多的判断,所以项目维持一个目前连接的设备,当然这种的没有考虑到mesh,如果mesh那会更加的复杂
-
11、BLE自动重连,在实际过程中,如果BLE断电然后上电,是需要重新连接的,但是建议做个延迟,因为我和硬件工程师联调的时候,发现App重连的指令太快,导致的秘钥协商不通过的问题
-
12、BLE回调的所有的消息其实都在一个线程池中,所以如果特别的场景,需要考虑到多线程同步的问题。。。
-
13、status =133 是个正常的现象,如果概率很大,需要做处理
Tip:
- 一个函数只做一件事情,它改做的事
- 一个函数,如果可能,让它保持在20行以内(优秀), 或者保持一个屏的高度以内(不错)
- 过于深度的if else逻辑问题, 要考虑抽取、分流的方式,并拆开层级
- 代码块不能粘在一起,关键位置隔行,以及组成代码块 或者 新的函数