Android BLE writeCharacteristic偶尔返回false的处理方法

背景:

最近在负责做RFID的一个项目,PDA通过BLE和BLE模块通信,BLE模块透传CMD给到RFID模块,然后RFID模块回Response,通过BLE模块给到PDA。

做好一些SDK的接口自己做压力测试的时候,发现很高频率的情况(例如间隔40,50ms或更短执行一次)下调用

 
  1. boolean status = mBluetoothGatt.writeCharacteristic(characteristic);

  2. Log.e("potter","status:"+status);

  3. 或者

  4. boolean status = mBluetoothGatt.readCharacteristic(characteristic);

  5. Log.e("potter","status:"+status);

返回值有时候是false,蓝牙模块也没有收到数据。正常情况是返回值为true,蓝牙模块收到数据。

追踪源码:

 
  1. BluetoothGatt.java类

  2. private Boolean mDeviceBusy = false;

 
  1. public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {

  2. synchronized (mDeviceBusy) {

  3. if (mDeviceBusy) return false;

  4. mDeviceBusy = true;

  5. }

  6. try {

  7. //这里才执行wirte操作

  8. mService.writeCharacteristic(mClientIf, device.getAddress(),

  9. characteristic.getInstanceId(), characteristic.getWriteType(),

  10. AUTHENTICATION_NONE, characteristic.getValue());

  11. } catch (RemoteException e) {

  12. Log.e(TAG, "", e);

  13. mDeviceBusy = false;

  14. return false;

  15. }

  16. }

 
  1. public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {

  2. synchronized (mDeviceBusy) {

  3. if (mDeviceBusy) return false;

  4. mDeviceBusy = true;

  5. }

  6. try {

  7. //这里才执行read操作

  8. mService.readCharacteristic(mClientIf, device.getAddress(),

  9. characteristic.getInstanceId(), AUTHENTICATION_NONE);

  10. } catch (RemoteException e) {

  11. Log.e(TAG, "", e);

  12. mDeviceBusy = false;

  13. return false;

  14. }

  15. }

 
  1. private final IBluetoothGattCallback mBluetoothGattCallback =

  2. new IBluetoothGattCallback.Stub() {

  3. ...

  4. public void onCharacteristicRead(String address, int status, int handle,

  5. byte[] value) {

  6. ...

  7. synchronized (mDeviceBusy) {

  8. mDeviceBusy = false;

  9. }

  10. ...

  11. runOrQueueCallback(new Runnable() {

  12. @Override

  13. public void run() {

  14. //对应我们自己code里面的回调

  15. final BluetoothGattCallback callback = mCallback;

  16. if (callback != null) {

  17. if (status == 0) characteristic.setValue(value);

  18. callback.onCharacteristicRead(BluetoothGatt.this, characteristic,

  19. status);

  20. }

  21. }

  22. });

  23. public void onCharacteristicWrite(String address, int status, int handle) {

  24. ...

  25. synchronized (mDeviceBusy) {

  26. mDeviceBusy = false;

  27. }

  28. ...

  29. runOrQueueCallback(new Runnable() {

  30. @Override

  31. public void run() {

  32. //对应我们自己code里面的回调

  33. final BluetoothGattCallback callback = mCallback;

  34. if (callback != null) {

  35. callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,

  36. status);

  37. }

  38. }

  39. });

  40. ...

  41.  
  42. }

原因

看了源码就一目了然了,当我们高频率的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内也没有定义类似

 
  1. public boolean isDeviceBusy(){

  2.  
  3. return mDeviceBusy;

  4.  
  5. }

的方法。

所以我们用反射来调用,完整代码如下:

 
  1. private static long HONEY_CMD_TIMEOUT = 2000;

  2. private boolean isDeviceBusy(){

  3. boolean state = false;

  4. try {

  5. state = (boolean)readField(mBluetoothGatt,"mDeviceBusy");

  6. Log.e("potter123","isDeviceBusy:"+state);

  7. } catch (IllegalAccessException e) {

  8. e.printStackTrace();

  9. } catch (NoSuchFieldException e) {

  10. e.printStackTrace();

  11. }

  12. return state;

  13. }

  14. public Object readField(Object object, String name) throws IllegalAccessException, NoSuchFieldException {

  15. Field field = object.getClass().getDeclaredField(name);

  16. field.setAccessible(true);

  17. return field.get(object);

  18. }

  19. public void writeCharacteristic(byte[] value,UUID serivceUUID,UUID characterUUID){

  20. if (mBluetoothGatt == null) {

  21. return;

  22. }

  23. BluetoothGattService service = null;

  24. long enterTime = System.currentTimeMillis();

  25. while ((System.currentTimeMillis() - enterTime) < HONEY_CMD_TIMEOUT) {

  26. if(isDeviceBusy()){

  27. try {

  28. Thread.sleep(10);

  29. } catch (InterruptedException e) {

  30. e.printStackTrace();

  31. }

  32. }else {

  33. break;

  34. }

  35. }

  36. try {

  37. service = mBluetoothGatt.getService(serivceUUID);

  38. BluetoothGattCharacteristic characteristic = service.getCharacteristic(characterUUID);

  39. characteristic.setValue(value);

  40. boolean status = mBluetoothGatt.writeCharacteristic(characteristic);

  41. Log.e("potter123","status:"+status);

  42. } catch (Exception e) {

  43. e.printStackTrace();

  44. }

  45. }

实测,即使间隔很短的调用我们给的接口。每次status都是true了。

3.自己参考mDeviceBusy的处理逻辑封装

参考mDeviceBusy去回调的onCharacteristicRead,onCharacteristicWrite定义自己的逻辑。

因为一般来说,要有比较严谨的设计,mDeviceBusy这个状态的控制是不够的,最好能做到每一条cmd的下发(writeCharacteristic),和response(notify后的onCharacteristicChanged)能做到一一对应,比如HEAD,LENGTH,CMD,CRC等标志位的处理逻辑等,但在这里就不一一赘述了。

总结:

用mDeviceBusy来做判断还是比较巧妙的,之前在网上也没有找到比较合适的solution。这说明求人不如求己,自己看源码还是有收获的!

你可能感兴趣的:(蓝牙BT)