我正在针对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()时,我展示了一个标识线程的 toast 。这两个线程具有不同的名称。我认为也许是由于某种原因在调用线程上调用了回调,但事实并非如此。那么线程在这里发生了什么?

最佳答案

这里的问题似乎是线程方面的一个模糊问题,在我的原始帖子中看不到,因为我没有张贴足够多的调用历史记录来查看它。我将解释我在这里发现的内容,以防其他人受到影响。

导致我的问题的完整通话记录如下:

  • 启动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()中进行 sleep 应该不会对BLE线程产生任何影响。 但是可以! 此更改没有影响。

    -----------编辑便笺--------------

    发布此内容后,我真的无法合理解释为什么上述匿名线程无法正常工作。所以我再次尝试了,这次确实可以工作。不知道第一次出了什么问题,也许我忘记了在线程上调用start()或其他什么东西。

    ---------结束编辑说明------------

    解决方案:

    最终,一时兴起,我决定在实例化我的类时创建一个背景HandlerThread(而不是在Thread中旋转一个匿名的onServicesDiscovered())。该方法现在如下所示:
    public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
        mBackgroundHandler.post(new Runnable() {
            @Override
            public void run() {
                doRead();
            }
        ).start();
    }
    

    上面版本的方法有效。读取上一个特性时,对doRead()的调用成功地遍历了每个特性。

    10-05 18:50