背景:
最近在负责做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。这说明求人不如求己,自己看源码还是有收获的!