onCharacteristicWrite
Android BLE写数据时,有一个onCharacteristicWrite
回调,这个回调失败的含义到底是什么呢?
大部分人首先想到的就是2.4G丢包导致的写数据失败。但是链路层本身是有校验和重发机制的,为什么这里还需要一个失败的回调呢?
根据BLE协议,射频链路层的重发机制对于ATT层和GATT层都是黑盒。链路层的重试机制一定会保证数据包以发送的顺序在对端被接收,如果数据发送失败则会无限次重试,直到发送成功或连接超时才会停止。不管是没有接收到射频信号或接收到了数据但CRC校验错误都算失败,区别是没有接收到射频信号会累加连接超时的计数器。
也就是说链路层的数据只存在发送成功(可能经过重发N次后成功),或者连接断开,不存在失败的可能性,也就无所谓返回状态的区别。
onCharacteristicWrite
返回的status状态看一下API就知道了,都是GATT开头的各种常量,也就是说从设备一定是接收到了数据,但是数据从链路层转发到GATT层的过程中可能存在各种异常情况,导致返回错误。需要注意的是应用层无法决定这个返回状态,比如说如果从设备的应用层接收到数据后发现不符合自己定义的接口数据协议规范,那么也只能通过另外发起notify来通知主设备,而无法通过status来返回错误类型。
也就是说,status的真正意义是告诉主设备,写入的信息是否已经被从设备的应用层接收到,仅此而已,这里失败的话是需要进行重发的,但失败的原因不是所谓的链路层丢包。
附带一下status的错误码,其实API里就能看到的。
GATT_ILLEGAL_PARAMETER 0x0087 (135)
GATT_NO_RESOURCES 0x0080 (128)
GATT_INTERNAL_ERROR 0x0081 (129)
GATT_WRONG_STATE 0x0082 (130)
GATT_DB_FULL 0x0083 (131)
GATT_BUSY 0x0084 (132)
GATT_ERROR 0x0085 (133)
GATT_CMD_STARTED 0x0086 (134)
GATT_PENDING 0x0088 (136)
GATT_AUTH_FAIL 0x0089 (137)
GATT_MORE 0x008a (138)
GATT_INVALID_CFG 0x008b (139)
GATT_SERVICE_STARTED 0x008c (140)
GATT_ENCRYPED_MITM
GATT_SUCCESS
GATT_ENCRYPED_NO_MITM 0x008d (141)
GATT_NOT_ENCRYPTED 0x008e (142)
writeCharacteristic
writeCharacteristic
的返回值代表Android的app是否把数据成功送达到手机的蓝牙芯片的数据缓冲区中,由于手机端蓝牙数据流控机制的存在,这里返回true也很可能是命令还在流控队列中排队,而不是真正的通过手机蓝牙射频信号发射出去,所以这个返回值几乎不可能是false,也没什么意义,实际上iOS系统下的对等函数就没有这个返回值。
那什么情况下,writeCharacteristic
会返回false呢?为了研究这个问题,我们先来看一段writeCharacteristic 的源码。
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false; if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid()); if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false; BluetoothGattService service = characteristic.getService(); if (service == null) return false; BluetoothDevice device = service.getDevice(); if (device == null) return false; synchronized(mDeviceBusy) { if (mDeviceBusy) return false; mDeviceBusy = true; } try { mService.writeCharacteristic(mClientIf, device.getAddress(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), characteristic.getWriteType(), AUTHENTICATION_NONE, characteristic.getValue()); } catch (RemoteException e) { Log.e(TAG,"",e); mDeviceBusy = false; return false; } return true; }
可以看到 其中一段代码:
synchronized(mDeviceBusy) { if (mDeviceBusy) return false; mDeviceBusy = true; }
如果mDeviceBusy,那么直接返回false,write操作不会继续
这个标记位显然用来标记device是否处于忙碌状态,让我们搜索一下,这个mDeviceBusy都在哪里设为true,又在哪里设为false。
1.write数据的时候,发数据下去的时候会设为true
2.read数据的时候,设为true
3. readDescriptor 时设为true
4. writeDesciptor 时设为true
5.executeReliableWrite 时设为true
6.onClientConnectionState 连接状态改变时 设为false
7. onCharacteristicWrite 写数据回调会设为false
8.onCharacteristicRead 写数据回调会设为false
9.onDescripterWrite会设为false
10.onDescripterRead会设为false
11.onExecuteWrite 会设为false
可以看出,在进行蓝牙数据读写(read,write等)会把mDeviceBusy设为true,表示正在忙碌,等待数据回调之后,设为false,表示空闲,只有空闲的时候,下一个蓝牙数据读写操作才可以进行,否则直接return false。 代表蓝牙操作是顺序执行的。
所以,当你没有间隔的多次writeCharacteristic
时候会发现只回调了一次onCharacteristicWrite
,原因就在于上一次的write操作还没有回调,蓝牙处于busy状态,没有执行更多的蓝牙操作。
那么如何解决这个问题呢?
方法1: 把多个连续的蓝牙操作(read,write等)放在线程里,并把每个蓝牙操作之间加延迟,sleep(200)类似这样。目的是等待回调完成之后再进行下一个蓝牙操作。这种方法不严谨,但比较简单,在一些逻辑简单的项目里可以使用
方法2:同步操作,把所有蓝牙操作同步,等待回调之后再进行下一个操作。
参考文献:
https://zhidao.baidu.com/question/649463150881060885.html http://a1anwang.com/post-18.html