I'm implementing a series of characteristic reads against a BLE device. Because readCharacteristic()
executes asynchronously, and because we have to wait until it completes before issuing another "read" call, I used a lock to wait()
and then in 'onCharacteristicRead()
I notify()
the lock to get things going again.
When I wait()
after calling readCharacteristic()
, I never get a call to onCharacteristicRead()
. If I don't wait()
, then I do get a call to onCharacteristicRead()
and the correct value is reported.
Here is the relevant code that seems to block the callback to onCharacteristicRead()
:
private void doRead() {
//....internal accounting stuff up here....
characteristic = mGatt.getService(mCurrServiceUUID).getCharacteristic(mCurrCharacteristicUUID);
isReading = mGatt.readCharacteristic(characteristic);
showToast("Is reading in progress? " + isReading);
showToast("On thread: " + Thread.currentThread().getName());
// Wait for read to complete before continuing.
while (isReading) {
synchronized (readLock) {
try {
readLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
showToast("onCharacteristicRead()");
showToast("On thread: " + Thread.currentThread().getName());
byte[] value = characteristic.getValue();
StringBuilder sb = new StringBuilder();
for (byte b : value) {
sb.append(String.format("%02X", b));
}
showToast("Read characteristic value: " + sb.toString());
synchronized (readLock) {
isReading = false;
readLock.notifyAll();
}
}
If I simply remove the while()
statement above, I successfully get the read callback. Of course, that prevents me from waiting to do further reads, so I can't move forward without waiting.
Given that the readCharacteristic()
is asynchronous, why would execution of the calling thread have anything to do with the ability to actually do the read, or the ability to call the callback?
To make things more confusing, I show a toast which identifies the thread when I call readCharacteristic()
, as well as when onCharacteristicRead()
is invoked. These 2 threads have different names. I thought that maybe the callback was being invoked on the calling thread for some reason, but that doesn't appear to be the case. So what is going on here with the threading?
The problem here appears to be an obscure issue with threading and it can't be seen in my original post because I didn't post enough of the call history to see it. I will explain what I found here in case it effects someone else.
The full call history leading to my problem went something like this:
discoverServices()
onServicesDiscovered()
doRead()
method in my original postreadCharacteristic()
onCharacteristicRead()
First Mistake:
Originally my onServicesDiscovered()
method looked like this:
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
doRead();
}
When doRead()
executes, it is going to sleep and therefore block execution. This prevents the callback method from finishing and apparently gunks up the entire BLE communicaiton system.
Second Mistake:
Once I realized the above issue, I changed the method to the following:
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
new Thread(new Runnable() {
@Override
public void run() {
doRead();
}
).start();
}
As far as I can tell, the above version of the method should work. I'm creating a new thread on which to run doRead()
, so sleeping in doRead()
should not have any impact on the BLE thread. But it does! This change had no impact.
----------- Edit Note --------------
After posting this, I really couldn't rationalize why the above anonymous thread wouldn't work. So I tried it again, and this time it did work. Not sure what went wrong the first time, maybe I forgot to call start()
on the thread or something...
--------- End Edit Note ------------
The Solution:
Finally, on a whim, I decided to create a background HandlerThread
when my class gets instantiated (instead of spinning up an anonymous Thread
in onServicesDiscovered()
). The method now looks like this:
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
mBackgroundHandler.post(new Runnable() {
@Override
public void run() {
doRead();
}
).start();
}
The above version of the method works. The call to doRead()
successfully iterates over each characteristic as the previous one is read.