Android BLE: onCharacteristicRead(), кажется, заблокирован нитью

Я реализую серию характеристических чтений против устройства BLE. Поскольку readCharacteristic() выполняется асинхронно, и потому, что нам нужно подождать, пока он не завершится до того, как будет выпущен другой "прочитанный" вызов, я использовал блокировку для wait(), а затем в 'onCharacteristicRead() я notify() блокировку, чтобы все было в порядке.

Когда я wait() после вызова readCharacteristic(), я никогда не получаю вызов onCharacteristicRead(). Если я не wait(), тогда я получаю вызов onCharacteristicRead() и сообщается правильное значение.

Вот соответствующий код, который, кажется, блокирует обратный вызов 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();
    }
}

Если я просто удалю оператор while() выше, я успешно получаю обратный вызов read. Конечно, это мешает мне ждать, чтобы читать дальше, поэтому я не могу двигаться вперед, не дожидаясь.

Учитывая, что readCharacteristic() является асинхронным, почему выполнение вызывающего потока имеет какое-либо отношение к способности выполнять чтение или возможность вызова обратного вызова?

Чтобы сделать вещи более запутанными, я показываю тост, который идентифицирует поток при вызове readCharacteristic(), а также при вызове onCharacteristicRead(). Эти 2 потока имеют разные имена. Я подумал, что, возможно, обратный вызов вызывался по вызывающему потоку по какой-то причине, но это, похоже, не так. Итак, что здесь происходит с потоком?

Ответ 1

Проблема здесь кажется неясной проблемой с потоковой обработкой, и ее нельзя увидеть в моем первоначальном сообщении, потому что я не опубликовал достаточно истории вызовов, чтобы увидеть ее. Я объясню, что я нашел здесь, если это повлияет на кого-то другого.

Полная история вызовов, ведущих к моей проблеме, прошла примерно так:

  • Запустить Le Scan
  • Найти устройство, которое меня волнует
  • Подключиться к устройству GATT-серверу (который возвращает клиент GATT и где я предоставляю BluetoothGattCallback для всех асинхронных вызовов связи)
  • Сообщите клиенту GATT discoverServices()
  • Через мгновение система BLE вызывает мой обратный вызов onServicesDiscovered()
  • Теперь я готов начать считывать характеристики, потому что загружаются данные службы, поэтому здесь я вызываю этот метод doRead() в своем исходном сообщении
  • Скажите клиенту GATT readCharacteristic()
  • Идите спать до тех пор, пока не будет прочитано ---- Это где тупик, но его предполагается:
  • Через мгновение система BLE вызывает мой callbacck onCharacteristicRead()
  • Уведомлять обо всех ожидающих потоках
  • Вернитесь к шагу 7 и повторите

Первая ошибка:

Первоначально мой метод onServicesDiscovered() выглядел следующим образом:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    doRead();
}

Когда doRead() выполняется, он будет спать и, следовательно, блокирует выполнение. Это предотвращает завершение метода обратного вызова и, по-видимому, запускает всю систему связи BLE.

Вторая ошибка:

Как только я понял вышеупомянутую проблему, я изменил метод на следующее:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            doRead();
        }
    ).start();
}

Насколько я могу судить, вышеуказанная версия метода должна работать. Я создаю новый поток для запуска doRead(), поэтому спать в doRead() не должно влиять на поток BLE. Но это так! Это изменение не повлияло.

----------- Редактировать примечание --------------

После публикации этого вопроса я действительно не мог рационализировать, почему вышеупомянутый анонимный поток не будет работать. Поэтому я попробовал еще раз, и на этот раз он работал. Не уверен, что пошло не так в первый раз, может быть, я забыл называть start() в потоке или что-то в этом роде...

--------- Конец Редактировать Примечание ------------

Решение:

Наконец, по прихоти, я решил создать фон HandlerThread, когда мой класс получает экземпляр (вместо поворота анонимного Thread в onServicesDiscovered()). Теперь метод выглядит следующим образом:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    mBackgroundHandler.post(new Runnable() {
        @Override
        public void run() {
            doRead();
        }
    ).start();
}

Вышеупомянутая версия метода работает. Вызов doRead() успешно повторяется по каждому признаку, поскольку предыдущий читается.

Ответ 2

Я решил эту проблему, добавив следующий код

        @Override
    public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        new Thread(new Runnable() {
            @Override
            public void run() {
                gatt.readCharacteristic(characteristic);
            }
        }).start();
    }