# 如何解决蓝牙订阅事件异常问题

#### 问题现象

如何解决蓝牙订阅事件异常，如出现订阅无法取消、订阅不生效和订阅后多次回调等问题？

#### 背景知识

每个订阅事件会创建一个独立的监听器实例。当订阅蓝牙开关状态事件access.on('stateChange')后，使用access.off('stateChange')取消订阅：

*   若传入参数callback，需要传入相同的回调函数，才能识别要移除具体的监听器。
*   若不传参，则取消订阅该事件对应的所有回调函数通知。

#### 问题定位

蓝牙订阅事件异常，主要分为以下三类场景：

1.  订阅事件后无法取消：
    
    例如，使用access.on('stateChange', this.onReceiveEvent)订阅蓝牙设备开关状态事件后，使用方式一可以正常取消订阅，而使用方式二却无法取消订阅。
    
    ```
    function onReceiveEvent(data: access.BluetoothState) {
      console.info(`bluetooth state = ${data}`);
    }
    // 订阅蓝牙设备开关状态事件
    access.on('stateChange', this.onReceiveEvent)
    
    // 方式一，取消订阅正常
    access.off('stateChange');
    // 方式二，无法取消订阅
    access.off('stateChange', (callback: access.BluetoothState) => {
      console.info('蓝牙开关取消订阅')
    });
    ```
    
2.  订阅事件不生效：
    
    *   排查是否在后台运行，应用线程是否被冻结。如果需要应用可以在后台稳定运行，需要开启蓝牙长时任务。
    *   排查事件是否已成功触发。
    *   排查应用订阅事件的时间，是否晚于触发事件的时间。
    
    例如，订阅on('BLECharacteristicChange')后，server端特征值内容变更通知client端，但未触发回调。
    
3.  订阅事件后多次回调：
    
    *   排查是否多次订阅事件，未及时取消监听，导致重复调用。
    *   部分事件本身会触发多次回调。
    
    例如，使用access.on('stateChange')订阅蓝牙设备开关状态事件后关闭蓝牙，会接收到多次蓝牙已关闭的状态。
    

#### 分析结论

1.  取消订阅蓝牙设备开关状态事件access.off('stateChange')传入的回调，需要和当时订阅on的回调函数是同一个，上述问题中，方式二的回调函数传的是匿名函数，与订阅事件注册的回调函数不一致，因此无法取消监听。正确写法为：
    
    ```
    // 方式二正确写法
    access.off('stateChange', this.onReceiveEvent);
    ```
    
2.  分析hilog日志，发现应用的订阅时间为14:16:50.545，对端设备回复消息的时间为14:16:50.543，对端设备已经回复过消息了，应用才订阅通知，因此订阅事件未能触发。
    
3.  订阅stateChange事件会监听所有BluetoothState状态变化，而不仅仅是最终的开启或关闭状态。关闭蓝牙时会经历STATE\_BLE\_TURNING\_OFF中间态，最终到达STATE\_OFF状态，每个状态变化都会触发回调。
    

#### 修改建议

1.  取消订阅时，确保传入与订阅时相同的回调函数，才能识别要移除具体的监听器。
2.  修改代码逻辑，确保在对端设备回复消息之前完成事件订阅。
3.  蓝牙关闭时出现的多次STATE\_OFF事件不用过多关注，出现一次就可以表明蓝牙已关闭，如果应用需要对蓝牙关闭时做一些特定的操作，仅执行一次即可。