背景:
最近在负责做RFID的一个项目,PDA通过BLE和BLE模块通信,BLE模块透传CMD给到RFID模块,然后RFID模块回Response,通过BLE模块给到PDA。
做好一些SDK的接口自己做压力测试的时候,发现很高频率的情况(例如间隔40,50ms或更短执行一次)下调用
boolean status = mBluetoothGatt.writeCharacteristic(characteristic);
Log.e("potter","status:"+status);
或者
boolean status = mBluetoothGatt.readCharacteristic(characteristic);
Log.e("potter","status:"+status);
返回值有时候是false,蓝牙模块也没有收到数据。正常情况是返回值为true,蓝牙模块收到数据。
追踪源码:
BluetoothGatt.java类
private Boolean mDeviceBusy = false;
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
…
synchronized (mDeviceBusy) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
…
try {
//这里才执行wirte操作
mService.writeCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), characteristic.getWriteType(),
AUTHENTICATION_NONE, characteristic.getValue());
} catch (RemoteException e) {
Log.e(TAG, "", e);
mDeviceBusy = false;
return false;
}
}
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
…
synchronized (mDeviceBusy) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
…
try {
//这里才执行read操作
mService.readCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG, "", e);
mDeviceBusy = false;
return false;
}
}
private final IBluetoothGattCallback mBluetoothGattCallback =
new IBluetoothGattCallback.Stub() {
...
public void onCharacteristicRead(String address, int status, int handle,
byte[] value) {
...
synchronized (mDeviceBusy) {
mDeviceBusy = false;
}
...
runOrQueueCallback(new Runnable() {
@Override
public void run() {
//对应我们自己code里面的回调
final BluetoothGattCallback callback = mCallback;
if (callback != null) {
if (status == 0) characteristic.setValue(value);
callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
status);
}
}
});
public void onCharacteristicWrite(String address, int status, int handle) {
...
synchronized (mDeviceBusy) {
mDeviceBusy = false;
}
...
runOrQueueCallback(new Runnable() {
@Override
public void run() {
//对应我们自己code里面的回调
final BluetoothGattCallback callback = mCallback;
if (callback != null) {
callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
status);
}
}
});
...
}
原因
看了源码就一目了然了,当我们高频率的writeCharacteristic或readCharacteristic的时候,返回false的时候就是DeviceBusy的时候。原因是writeCharacteristic后,对应的onCharacteristicWrite还没执行到,第二次writeCharacteristic就来了,此时mDeviceBusy的值是true,就直接返回false了。
解决方法:
1.设置两条CMD间的间隔
实测100ms还是比较稳定的,对外的接口设计成,例如每发送一条CMD后,Thread.sleep(100)。注意提示客户别多线程调用接口就ok了。
但是这样设计其实是有风险的,可能不同的BLE模块的差异,现场环境的差异,或者PDA的差异导致实际比较可靠的间隔大于100ms,或者说本来80ms就够了的,但是却等了100ms,从代码的角度来考虑不够优雅。
2.根据mDeviceBusy这个值来做文章
我们设置一个超时,每次writeCharacteristic前,超时范围内不断的读mDeviceBusy,当Device不是busy的时候,跳出循环,去执行writeCharacteristic。
比较蛋疼是mDeviceBusy是private的,外部拿不到。
BluetoothGatt.java内也没有定义类似
public boolean isDeviceBusy(){
return mDeviceBusy;
}
的方法。
所以我们用反射来调用,完整代码如下:
private static long HONEY_CMD_TIMEOUT = 2000;
private boolean isDeviceBusy(){
boolean state = false;
try {
state = (boolean)readField(mBluetoothGatt,"mDeviceBusy");
Log.e("potter123","isDeviceBusy:"+state);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return state;
}
public Object readField(Object object, String name) throws IllegalAccessException, NoSuchFieldException {
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
public void writeCharacteristic(byte[] value,UUID serivceUUID,UUID characterUUID){
if (mBluetoothGatt == null) {
return;
}
BluetoothGattService service = null;
long enterTime = System.currentTimeMillis();
while ((System.currentTimeMillis() - enterTime) < HONEY_CMD_TIMEOUT) {
if(isDeviceBusy()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
try {
service = mBluetoothGatt.getService(serivceUUID);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(characterUUID);
characteristic.setValue(value);
boolean status = mBluetoothGatt.writeCharacteristic(characteristic);
Log.e("potter123","status:"+status);
} catch (Exception e) {
e.printStackTrace();
}
}
实测,即使间隔很短的调用我们给的接口。每次status都是true了。
3.自己参考mDeviceBusy的处理逻辑封装
参考mDeviceBusy去回调的onCharacteristicRead,onCharacteristicWrite定义自己的逻辑。
因为一般来说,要有比较严谨的设计,mDeviceBusy这个状态的控制是不够的,最好能做到每一条cmd的下发(writeCharacteristic),和response(notify后的onCharacteristicChanged)能做到一一对应,比如HEAD,LENGTH,CMD,CRC等标志位的处理逻辑等,但在这里就不一一赘述了。
总结:
用mDeviceBusy来做判断还是比较巧妙的,之前在网上也没有找到比较合适的solution。这说明求人不如求己,自己看源码还是有收获的!