如何通过读/写BLE Gatt特性实现最大线程安全性?

时间:2016-08-12 16:27:54

标签: android multithreading bluetooth-lowenergy

我正在与BLE设备通信,该设备通过一个特征向我发送大量数据。使用相同的CH将数据发送到设备。

内部Androids BluetoothGattCharacteristic有方法

public byte[] getValue() {
    return mValue;
}

public boolean setValue(byte[] value) {
    mValue = value;
    return true;
}

但是,执行是从不同的线程发生的。 Android运行大约5个不同的绑定线程,他们调用

onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

我现在尝试将数组作为回调中的第一个操作,但不能保证另一个线程(不在我的控制之下)同时设置数组。

虽然上面似乎可以解决问题,但更复杂的问题是针对传入的数据流发送数据'。

我必须使用相同的CH将数据发送到设备,因此我setValue()然后BluetoothGatt.writeCharacteristic

public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
// some null checks etc

//now it locks the device
synchronized(mDeviceBusy) {
    if (mDeviceBusy) return false;
    mDeviceBusy = true;
}

//the actual execution

return true;
}

然后我会在某个时候收到来自某个线程的回调

onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 

然而,当我抓住该值并尝试检查它是否是我想要发送的内容时,有时它已经是一个刚刚从其他线程更新的已接收包。

如果不访问Androids BLE API或堆栈等,我怎样才能使这更安全?

2 个答案:

答案 0 :(得分:2)

在SDK27中, BluetoothGatt.java 中的onNotify()回调函数已更新,可以在调用中同时调用BluetoothGattCharacteristic.setValue()BluetoothGattCallback.onCharacteristicChanged() Runnable的 run()

此更改使我们能够将对BluetoothGattCharacteristic.setValue()的所有调用(用于我们向特征的出站写入和入站通知)都强制到同一线程,从而消除了竞争条件破坏BluetoothGattCharacteristic.mValue的情况; < / p>

  1. 创建一个HandlerThread
  2. 在您的Handler上附加一个HandlerThread
  3. 将您的Handler传递到BluetoothDevice.connectGatt()-恭喜,收到通知后,setValue()onCharacteristicChanged()将在您的HandlerThread上被呼叫。
  4. 要编写特征时,请通过处理程序将setValue()writeCharacteristic()发布到HandlerThread

现在,竞争条件中涉及的所有函数调用都在同一线程上执行,从而消除了竞争条件。

答案 1 :(得分:0)

在SDK27之前,似乎不可能使用公共API通过单一特性进行可靠的全双工通信。

很烦人。

但是,如果您愿意作弊,似乎有一种方法。 (作弊的方法可能不止一种;下面描述的方法影响很小。)

问题是将BluetoothGattCharacteristic中的mValue字段用于两个冲突目的-发送和接收。 API设计相当糟糕; API级别的解决方法是引入另一个字段,以便每个方向都有一个。另一个可能是在发送数据时不使用setValue()方法,而是将有问题的数据作为writeCharacteristic()的参数提供(尽管由于在get / set期间可能对value字段进行编码的事实,这很复杂,支持几种数据类型)。但是,API维护者似乎选择了其他路径。

无论如何-要解决此问题,请确保将接收到的值和要发送的值存储在不同的位置。更具体地说,在BluetoothGattCharacteristic的两个不同实例的值字段中。

现在,无法使用官方API克隆BGC。 使用公共构造函数,您可以获取一个实例,该实例的所有字段均已设置,但serviceinstanceId除外。这些都有公共获取器-设置它们,使用反射访问setService()setInstanceId()-这是欺骗性的部分。

我已经测试了这种方法[1],它似乎可以按需工作。 :-)

[1]实际上是这种方法的一种变体;在较新的SDK版本中,所讨论的类是Parcelable,因此,在可能的情况下,我会通过Parcel序列化来克隆对象,以防万一将来的实现会添加更多字段。这将处理除service字段以外的所有内容,您仍然需要使用反射进行设置。