android关闭蓝牙连接手机号码,Android BLE蓝牙连接要注意的问题

一、运行时问题

发起请求

手机和蓝牙设备建立连接,不论是在哪个进程,哪个线程发起的请求,最终都要丢到系统蓝牙服务进程中去处理。

看到有的文章说在同一个进程中,发起的连接和读写等请求最好都在UI线程里,这种说法我不认同。原因有两点:

1,不论发起请求是在UI线程还是子线程,都只是封装成数据结构丢给系统蓝牙服务进程,这种跨进程通信最终会调到系统蓝牙服务进程的Binder线程池中,然后再由底层的蓝牙框架分发到不同的线程中去处理请求,因此上层调用方在哪个线程对底层来说没太大关系。

2,这种跨进程调用最好不要放在UI线程里,我们知道跨进程调用时调用方会被暂时挂起,直到处理方请求处理完毕。通常来说处理方处理请求会是很快的,如果是耗时的请求就异步处理,这样能保证调用方挂起的时间很短暂。然而当系统负载很重时,这种跨进程通信的调度延时会增加,可能导致UI界面卡顿甚至ANR。

建议的做法是统一分派到一个子线程中去发起所有的请求,即便对于多个设备也是如此,没有必要每个设备一个线程。

请求回调

当系统蓝牙服务处理请求之后,有状态更新时会以回调的方式通知给调用方,比如连接上了,或连接断开了,或数据读到了,或数据写成功了,或设备有数据推送过来了等等。

要注意的是这些回调都不是在UI线程,而是在Binder线程池中。一定要注意到这一点,因为调用方和回调方不在同一个线程,可能存在数据不一致的问题,要么上锁,要么post到同一个线程再处理。

二、异步调用问题

蓝牙的所有请求基本都是异步调用的,对同一个设备,其所有通信过程只能串行,不能并行。如不能同时发起读和写的异步调用,不能同时发起多个character的异步读调用等。必须等到上一个操作的回调之后再发起下一个操作。因此我们需要维护一个任务队列,自动将同一个设备的所有操作串行化。对于多设备的情况,每个设备维护一个任务队列。不过多个设备都共享一个请求分派子线程。

例外的是closeGatt,这个无需放在队列中,当要断开设备连接时,先清空任务队列,再closeGatt。

另外还需对每个调用做超时检查,有时候蓝牙协议栈异常可能收不到回调,如果不做超时检查,后面的所有操作都被堵塞了。对于这些超时的任务,通常是连接出了问题,建议的做法是直接给gatt关掉,下次重新连接的时候重开一个gatt。

三、缓存问题

可能有人想过设备的service既然不变,为什么每次连接都要discover service,不如缓存起来,设备重连时直接用,但实际情况不能这样做。service不能缓存,虽然uuid什么的都没变,但是这些service都会和gatt关联的,如果gatt变了,那service就报废了,对这些service和character做任何读写操作都会出错。所以建议每次连接上时都去discover service,不要缓存。

当设备固件升级后,character可能发生了变化,而系统蓝牙协议栈是不知道的,下次discover service的时候还是返回的旧的缓存,这样读写character可能会失败。解决办法是固件升级后,断开连接再重开一个gatt,并马上刷新一下该设备的缓存。当然,重启蓝牙也会刷新缓存,不过会影响到所有设备。另外有时候discover service服务发现的不全,或者根本发现不了服务,也可以考虑清除一下缓存。

关于缓存的清除可以参考

四、连接释放

设备的GATT句柄在不用时要及时关闭,不然会造成连接泄露,而系统支持的连接句柄数是有限的,如果没记错的话是32个,当达到上限后其它设备就连不了了。注意这是系统全局性的,如果你所在APP占用了过多GATT句柄,其它的APP进程就可能无法成功申请GATT句柄了。因为不论调用方在哪个进程,最终都是由系统服务进程来分配GATT句柄的。

当设备断开连接时,最好closeGatt,而不是diconnect。不要下次复用之前的gatt来reconnect,因为有的手机上重连可能会存在问题,比如重连后死活发现不了service。这种情况下,最好只要断开连接就close gatt,下次连接时打开全新的gatt,这样就可以发现service了。

每个GATT背后都有一套对应的状态机,各种异常基本上除了连接本身之外都是状态机出了问题,因此直接关掉当前GATT,重开一个GATT就能解决大部分问题。就如同解决大部分软件异常,最直接的办法就是重启一样。

你可能感兴趣的:(android关闭蓝牙连接手机号码,Android BLE蓝牙连接要注意的问题)