我正在与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或堆栈等,我怎样才能使这更安全?
答案 0 :(得分:2)
在SDK27中, BluetoothGatt.java 中的onNotify()
回调函数已更新,可以在调用中同时调用BluetoothGattCharacteristic.setValue()
和BluetoothGattCallback.onCharacteristicChanged()
Runnable的 run()
。
此更改使我们能够将对BluetoothGattCharacteristic.setValue()
的所有调用(用于我们向特征的出站写入和入站通知)都强制到同一线程,从而消除了竞争条件破坏BluetoothGattCharacteristic.mValue
的情况; < / p>
HandlerThread
Handler
上附加一个HandlerThread
Handler
传递到BluetoothDevice.connectGatt()
-恭喜,收到通知后,setValue()
和onCharacteristicChanged()
将在您的HandlerThread
上被呼叫。setValue()
和writeCharacteristic()
发布到HandlerThread
现在,竞争条件中涉及的所有函数调用都在同一线程上执行,从而消除了竞争条件。
答案 1 :(得分:0)
在SDK27之前,似乎不可能使用公共API通过单一特性进行可靠的全双工通信。
很烦人。
但是,如果您愿意作弊,似乎有一种方法。 (作弊的方法可能不止一种;下面描述的方法影响很小。)
问题是将BluetoothGattCharacteristic中的mValue
字段用于两个冲突目的-发送和接收。 API设计相当糟糕; API级别的解决方法是引入另一个字段,以便每个方向都有一个。另一个可能是在发送数据时不使用setValue()方法,而是将有问题的数据作为writeCharacteristic()的参数提供(尽管由于在get / set期间可能对value字段进行编码的事实,这很复杂,支持几种数据类型)。但是,API维护者似乎选择了其他路径。
无论如何-要解决此问题,请确保将接收到的值和要发送的值存储在不同的位置。更具体地说,在BluetoothGattCharacteristic的两个不同实例的值字段中。
现在,无法使用官方API克隆BGC。
使用公共构造函数,您可以获取一个实例,该实例的所有字段均已设置,但service
和instanceId
除外。这些都有公共获取器-设置它们,使用反射访问setService()
和setInstanceId()
-这是欺骗性的部分。
我已经测试了这种方法[1],它似乎可以按需工作。 :-)
[1]实际上是这种方法的一种变体;在较新的SDK版本中,所讨论的类是Parcelable,因此,在可能的情况下,我会通过Parcel序列化来克隆对象,以防万一将来的实现会添加更多字段。这将处理除service
字段以外的所有内容,您仍然需要使用反射进行设置。