简体   繁体   English

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

[英]How could I achieve maximum thread safety with a read/write BLE Gatt Characteristic?

I am communicating with a BLE device that sends me lots of data via one characteristic.我正在与一个 BLE 设备通信,该设备通过一个特性向我发送大量数据。 The same Characteristic is used to send data to the device.相同的特性用于向设备发送数据。

Inside Androids BluetoothGattCharacteristic there are the methods在 Androids BluetoothGattCharacteristic有方法

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

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

However, the execution happens from different threads.但是,执行发生在不同的线程中。 Android runs about 5 different binder-threads and they call Android 运行大约 5 个不同的绑定线程,它们调用

onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

I now try to grab the array as first operation in the callback, but it is NOT guaranteed that another thread (not under my control) is setting the array at the same time.我现在尝试抓取数组作为回调中的第一个操作,但不能保证另一个线程(不在我的控制之下)同时设置数组。

While above seems to do the trick, a more complicated matter is sending data 'against an incoming stream of data'.虽然上面似乎可以解决问题,但更复杂的问题是“针对传入的数据流”发送数据。

I have to use the same Characteristic to send data down to the device, so I setValue() and then BluetoothGatt.writeCharacteristic .我必须使用相同的 Characteristic 将数据发送到设备,所以我先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;
}

I will then at some point receive a callback from some thread然后我会在某个时候从某个线程接收回调

onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 

However, when I grab the value and try to check if it is what I wanted to send, sometimes it already is a received package that has just been updated from some other thread.但是,当我获取值并尝试检查它是否是我想要发送的内容时,有时它已经是一个刚刚从其他线程更新的接收包。

How could i make this more thread-safe without having access to Androids BLE API or the stack etc ?在无法访问 Androids BLE API 或堆栈等的情况下,我怎样才能使这更加线程安全?

In SDK27, the onNotify() callback function in BluetoothGatt.java was updated to call BOTH BluetoothGattCharacteristic.setValue() and BluetoothGattCallback.onCharacteristicChanged() in the Runnable's run() .在 SDK27 中, BluetoothGatt.java 中onNotify()回调函数更新为在Runnable 的run()同时调用BluetoothGattCharacteristic.setValue()BluetoothGattCallback.onCharacteristicChanged() run()

This change allows us to force all calls to BluetoothGattCharacteristic.setValue() - both for our outbound writing to the characteristic and the inbound notifications - onto the same thread, which eliminates the race condition corrupting the BluetoothGattCharacteristic.mValue ;此更改允许我们强制所有对BluetoothGattCharacteristic.setValue()调用 - 用于我们对特征的出站写入和入站通知 - 到同一线程上,这消除了破坏BluetoothGattCharacteristic.mValue的竞争条件;

  1. Create a HandlerThread创建一个处理HandlerThread
  2. Create a Handler attached to your HandlerThread创建一个附加到您的HandlerThreadHandler
  3. Pass your Handler into BluetoothDevice.connectGatt() - congratulations, when a notify is received the setValue() and onCharacteristicChanged() will be called on your HandlerThread .将您的Handler传递到BluetoothDevice.connectGatt() - 恭喜,当收到通知时,将在您的HandlerThread上调用setValue()onCharacteristicChanged()
  4. When you want to write to the characteristic, post your setValue() and writeCharacteristic() to your HandlerThread via your Handler当您想写入特征时,请通过您的 Handler 将您的setValue()writeCharacteristic()到您的HandlerThread

Now all the function calls that were involved in the race condition are being executed on the same thread, eliminating the race condition.现在所有涉及竞争条件的函数调用都在同一个线程上执行,消除了竞争条件。

Before SDK27, it appears to be impossible to have reliable full duplex communication over a single characteristic - using the public API.在 SDK27 之前,似乎不可能通过单个特征进行可靠的全双工通信 - 使用公共 API。

Quite vexing.挺烦人的。

There appears to be a way, though, if you're willing to cheat a little.不过,似乎有一种方法,如果您愿意作弊的话。 (There is probably more than one way to cheat; the one described below is fairly low-impact.) (作弊的方法可能不止一种;下面描述的一种影响相当低。)

The problem is the use of the mValue field in BluetoothGattCharacteristic for two conflicting purposes - send and receive.问题是将 BluetoothGattCharacteristic 中的mValue字段用于两个相互冲突的目的 - 发送和接收。 It is fairly bad API design;这是相当糟糕的 API 设计; one API-level fix would have been to introduce another field, so that there would be one for each direction.一个 API 级别的修复将是引入另一个字段,以便每个方向都有一个。 Another would have been to not use the setValue() method when sending data, but instead provided the data in question as a parameter to writeCharacteristic() (though this is complicated by the fact that the value field may be encoded during get/set, supporting several data types).另一种方法是在发送数据时不使用 setValue() 方法,而是将有问题的数据作为参数提供给 writeCharacteristic()(尽管这很复杂,因为值字段可能在 get/set 期间被编码,支持多种数据类型)。 However, it appears that the API maintainers have chosen a different path.但是,API 维护者似乎选择了不同的路径。

Anyway - to fix the problem, ensure that the received value and the value-to-be-sent are stored in different locations.无论如何 - 要解决问题,请确保接收到的值和要发送的值存储在不同的位置。 More specifically, in the value fields of two different instances of BluetoothGattCharacteristic.更具体地说,在 BluetoothGattCharacteristic 的两个不同实例的值字段中。

Now, cloning a BGC is not possible using the official API.现在,无法使用官方 API 克隆 BGC。 Using the public constructor, you can get an instance which has all fields set except service and instanceId .使用公共构造函数,您可以获得一个实例,该实例设置了除serviceinstanceId之外的所有字段。 These have public getters - to set them, use reflection to access setService() and setInstanceId() - this is the cheaty part.这些具有公共 getter - 设置它们,使用反射访问setService()setInstanceId() - 这是作弊部分。

I have tested this approach[1], and it appears to work as desired.我已经测试了这种方法[1],它似乎按预期工作。 :-) :-)

[1] A variation of this approach, in fact; [1] 实际上是这种方法的一种变体; in newer SDK versions, the class in question is Parcelable, so when possible I clone the object through Parcel-serialization, just in case future implementations add more fields.在较新的 SDK 版本中,有问题的类是 Parcelable,因此如果可能,我会通过 Parcel 序列化克隆对象,以防将来的实现添加更多字段。 This takes care of everything except the service field, which you still need to set using reflection.这会处理除service字段之外的所有内容,您仍然需要使用反射进行设置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM