在这里插入图片描述

微信小程序BLE(低功耗蓝牙)连接易受设备信号、系统限制、环境干扰等因素影响而异常断开,合理的异常检测的自动重连机制能大幅提升用户体验。结合微信原生API与实战经验,提供可直接落地的实现方案,覆盖异常检测、分级重连、容错处理全流程,适配iOS/Android/鸿蒙多平台。

一、BLE连接基础配置

实现异常检测与重连前,需完成蓝牙适配器初始化、设备连接等基础操作,同时全局维护连接状态与设备信息,为后续逻辑提供支撑。

// 全局状态管理(可放在页面data或全局变量中)
Page({
  data: {
    deviceId: '', // 目标设备ID(连接成功后存储)
    connected: false, // 连接状态标识
    shouldReconnect: true, // 是否允许自动重连(手动断开时设为false)
    retryCount: 0, // 重连次数计数器
    maxRetry: 5, // 最大重连次数
    heartbeatTimer: null, // 心跳检测定时器
    discoveryTimer: null // 设备搜索定时器
  },

  // 1.初始化蓝牙适配器(入口方法)
  initBLE() {
    wx.openBluetoothAdapter({
      success: (res) => {
        console.log('蓝牙适配器初始化成功', res);
        this.startDeviceDiscovery(); // 初始化成功后搜索设备
        this.listenConnectionState(); // 开启连接状态监听(核心检测手段)
      },
      fail: (err) => {
        console.error('蓝牙初始化失败', err);
        // 处理初始化错误(如蓝牙未开启、设备不支持)
        this.handleBLEError(err);
      }
    });
  },

  // 2.搜索目标设备(简化版,可根据实际需求过滤)
  startDeviceDiscovery() {
    wx.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false, // 不重复上报设备
      interval: 2000, // 上报间隔2秒
      success: () => {
        // 5秒后自动停止搜索,避免过度耗电
        this.data.discoveryTimer = setTimeout(() => {
          wx.stopBluetoothDevicesDiscovery();
        }, 5000);

        // 监听设备发现事件,过滤目标设备
        wx.onBluetoothDeviceFound((res) => {
          const validDevices = res.devices.filter(device => 
            device.name?.includes('目标设备名称') || device.localName?.includes('设备标识')
          );
          if (validDevices.length > 0) {
            const targetDevice = validDevices[0];
            this.setData({ deviceId: targetDevice.deviceId });
            this.connectBLEDevice(targetDevice.deviceId); // 发现目标设备后立即连接
            wx.stopBluetoothDevicesDiscovery(); // 停止搜索
            clearTimeout(this.data.discoveryTimer);
          }
        });
      },
      fail: (err) => {
        console.error('搜索设备失败', err);
      }
    });
  },

  // 3.建立BLE连接
  connectBLEDevice(deviceId) {
    wx.createBLEConnection({
      deviceId,
      timeout: 8000, // 连接超时时间(8秒)
      success: (res) => {
        console.log('BLE连接成功', res);
        this.setData({
          connected: true,
          retryCount: 0 // 连接成功后重置重连计数器
        });
        // 连接成功后立即获取服务和特征值(后续数据交互需用到)
        this.getBLEDeviceServices(deviceId);
        // 启动心跳检测,主动监测连接稳定性
        this.startHeartbeat();
      },
      fail: (err) => {
        console.error('BLE连接失败', err);
        this.handleBLEError(err); // 连接失败触发错误处理,执行重连
      }
    });
  }
})```

## 二、异常断开检测:3种方式

异常断开分为「主动断开」和「被动断开」,需通过多重监听确保无遗漏,核心依赖微信原生API与主动心跳检测结合。
方式1:连接状态变化监听(核心,必加)
通过wx.onBLEConnectionStateChange监听设备连接状态变化,可捕获绝大多数被动断开场景(如设备断电、信号中断、系统强制断开),是最基础的检测手段,基础库2.9.2及以上支持,鸿蒙系统也可兼容。
```javascript
// 监听BLE连接状态变化(全局只需要调用一次)
listenConnectionState() {
  wx.onBLEConnectionStateChange((res) => {
    console.log(`设备${res.deviceId}连接状态变化:${res.connected ? '已连接' : '已断开'}`);
    // 同步更新全局连接状态
    this.setData({ connected: res.connected });

    // 检测到异常断开,且允许重连时,触发自动重连
    if (!res.connected && this.data.shouldReconnect && this.data.deviceId) {
      console.warn('检测到异常断开,准备自动重连');
      this.autoReconnect();
    }
  });
}```
方式2:错误码监听(精准定位断开原因)
BLE相关API调用失败时,会返回errCode或errno(优先使用errno,统一规范),其中部分错误码直接对应连接断开,需单独捕获处理,常见断开相关错误码如下:
•10006:当前连接已断开(最常见,读写操作时易触发)
•1509001:连接BLE设备失败
•1509003:未连接上该BLE设备
•1509005:BLE操作超时(间接导致断开)
封装错误处理器,统一处理断开相关错误:
```javascript
// BLE错误统一处理(适配errCode和errno)
handleBLEError(err) {
  console.error('BLE错误详情', err);
  const errCode = err.errno || err.errCode; // 优先使用errno(统一规范)
  const errorMap = {
    10001: { msg: '蓝牙未开启', action: 'openBluetooth' },
    10006: { msg: '连接已断开', action: 'reconnect' },
    1509001: { msg: '连接设备失败', action: 'reconnect' },
    1509003: { msg: '未连接设备', action: 'reconnect' },
    1509005: { msg: '操作超时', action: 'reconnect' },
    1500105: { msg: '系统不支持BLE', action: 'tips' }
  };

  const strategy = errorMap[errCode] || { msg: err.errMsg, action: 'none' };
  switch (strategy.action) {
    case 'openBluetooth':
      // 引导用户开启蓝牙
      wx.showModal({
        title: '提示',
        content: '蓝牙未开启,请开启蓝牙后重试',
        success: (res) => {
          if (res.confirm) {
            wx.openBluetoothAdapter();
          }
        }
      });
      break;
    case 'reconnect':
      // 触发自动重连
      if (this.data.shouldReconnect && this.data.deviceId) {
        this.autoReconnect();
      }
      break;
    case 'tips':
      wx.showToast({ title: strategy.msg, icon: 'none' });
      break;
  }
}```
方式3:心跳检测(主动监测,解决隐性断开)
部分场景下,连接状态未触发变化,但设备已失去通信能力(隐性断开),需通过定时心跳检测确认连接有效性。核心逻辑:定时向设备发送心跳指令,若发送失败则判定为断开,触发重连。
```javascript
// 启动心跳检测(连接成功后调用)
startHeartbeat() {
  // 清除之前的心跳定时器,避免重复创建
  if (this.data.heartbeatTimer) {
    clearInterval(this.data.heartbeatTimer);
  }
  // 每30秒发送一次心跳指令(时间可根据设备调整)
  this.data.heartbeatTimer = setInterval(() => {
    if (!this.data.connected) return; // 未连接时不发送心跳

    // 发送心跳指令(需根据设备协议调整serviceId和characteristicId)
    this.writeBLEHeartbeat().catch((err) => {
      console.warn('心跳发送失败,判定为连接断开', err);
      this.setData({ connected: false });
      this.autoReconnect(); // 心跳失败触发重连
    });
  }, 30000);
},

// 发送心跳指令(示例,需适配设备实际协议)
writeBLEHeartbeat() {
  const { deviceId } = this.data;
  // 假设心跳指令为0x01(需与硬件协商)
  const heartbeatBuffer = new Uint8Array([0x01]).buffer;
  return new Promise((resolve, reject) => {
    wx.writeBLECharacteristicValue({
      deviceId,
      serviceId: '0000FFF0-0000-1000-8000-00805F9B34FB', // 设备服务UUID
      characteristicId: '0000FFF6-0000-1000-8000-00805F9B34FB', // 写入特征值UUID
      value: heartbeatBuffer,
      success: () => resolve(),
      fail: (err) => reject(err)
    });
  });
}```

## 三、自动重连实现

重连需遵循「指数退避」原则,避免频繁重试导致设备或小程序资源占用过高,同时设置最大重试次数,失败后提示用户,提升体验。核心实现分级重连:即时重连→定时重连→页面唤醒重连。
核心重连方法(含指数退避算法)
```javascript
// 自动重连核心方法(指数退避,避免频繁重试)
autoReconnect() {
  const { deviceId, retryCount, maxRetry } = this.data;
  // 超过最大重试次数,停止重连并提示用户
  if (retryCount >= maxRetry) {
    this.setData({ shouldReconnect: false });
    wx.showToast({
      title: `重连失败(已重试${maxRetry}次)`,
      icon: 'none',
      duration: 2000
    });
    // 提示用户手动重新连接
    wx.showModal({
      title: '连接失败',
      content: '设备连接不稳定,请检查设备状态后重试',
      confirmText: '重新连接',
      success: (res) => {
        if (res.confirm) {
          this.setData({ retryCount: 0, shouldReconnect: true });
          this.autoReconnect(); // 重置计数器后重新重试
        }
      }
    });
    return;
  }

  // 指数退避:重试间隔随次数递增(1s→2s→4s→8s...)
  const delay = Math.pow(2, retryCount) * 1000;
  console.log(`${retryCount+1}次重连,延迟${delay}ms`);

  setTimeout(() => {
    wx.createBLEConnection({
      deviceId,
      timeout: 8000,
      success: (res) => {
        console.log('重连成功', res);
        this.setData({
          connected: true,
          retryCount: 0
        });
        this.getBLEDeviceServices(deviceId);
        this.startHeartbeat(); // 重连成功重启心跳
      },
      fail: (err) => {
        console.error(`${retryCount+1}次重连失败`, err);
        // 重试次数+1,继续重连
        this.setData({ retryCount: retryCount + 1 });
        this.autoReconnect();
      }
    });
  }, delay);
}```
补充:页面生命周期联动(提升重连覆盖率)
小程序进入后台后,系统可能释放蓝牙连接,需在页面显示时检测连接状态,主动重连;页面隐藏时停止心跳,避免耗电。
```javascript
// 页面显示时,检测连接状态
onShow() {
  const { deviceId, connected, shouldReconnect } = this.data;
  // 有设备ID、未连接、允许重连时,主动检测并重连
  if (deviceId && !connected && shouldReconnect) {
    this.checkConnection();
  }
},

// 页面隐藏时,停止心跳和搜索,避免耗电
onHide() {
  if (this.data.heartbeatTimer) {
    clearInterval(this.data.heartbeatTimer);
  }
  if (this.data.discoveryTimer) {
    clearTimeout(this.data.discoveryTimer);
  }
  wx.stopBluetoothDevicesDiscovery();
},

// 主动检查连接状态(页面唤醒时调用)
checkConnection() {
  const { deviceId } = this.data;
  wx.getBLEDeviceConnectionState({
    deviceId,
    success: (res) => {
      if (!res.connected) {
        console.warn('页面唤醒,检测到连接断开,触发重连');
        this.autoReconnect();
      } else {
        this.setData({ connected: true });
        this.startHeartbeat(); // 已连接则重启心跳
      }
    },
    fail: (err) => {
      console.error('检查连接状态失败', err);
      this.autoReconnect();
    }
  });
}```

## 四、关键避坑要点

•1. 避免重复连接:安卓端重复调用wx.createBLEConnection可能导致系统持有多个连接实例,断开时无法彻底释放,需在连接前先调用wx.getBLEDeviceConnectionState检查状态,已连接则不重复调用。
•2. 平台兼容性:iOS会缓存蓝牙搜索结果,安卓每次扫描都是全新结果;iOS后台5秒后可能释放蓝牙连接,需在onShow时加强检测。
•3. 权限处理:Android端搜索蓝牙需开启GPS定位权限,需在小程序配置中声明,并引导用户授权;iOS需在info.plist中声明位置权限。
•4. 资源释放:页面卸载时(onUnload),需清除所有定时器、关闭蓝牙连接、停止搜索,避免内存泄漏。
•5. 设备过滤:搜索设备时,通过设备名称、localName或serviceUUID过滤无关设备,减少干扰,提升连接效率。
五、完整代码参考
将上述方法整合,形成完整的页面代码,可根据实际设备的serviceUUID、特征值UUID、心跳指令调整参数。
```javascript
Page({
  data: {
    deviceId: '',
    connected: false,
    shouldReconnect: true,
    retryCount: 0,
    maxRetry: 5,
    heartbeatTimer: null,
    discoveryTimer: null,
    serviceId: '0000FFF0-0000-1000-8000-00805F9B34FB', // 替换为设备实际serviceUUID
    writeCharId: '0000FFF6-0000-1000-8000-00805F9B34FB' // 替换为设备实际写入特征值UUID
  },

  onLoad() {
    this.initBLE(); // 页面加载时初始化蓝牙
  },

  onShow() {
    const { deviceId, connected, shouldReconnect } = this.data;
    if (deviceId && !connected && shouldReconnect) {
      this.checkConnection();
    }
  },

  onHide() {
    this.stopAllTimers();
    wx.stopBluetoothDevicesDiscovery();
  },

  onUnload() {
    this.stopAllTimers();
    this.closeBLEConnection();
  },

  // 初始化蓝牙适配器
  initBLE() {
    wx.openBluetoothAdapter({
      success: () => {
        console.log('蓝牙初始化成功');
        this.startDeviceDiscovery();
        this.listenConnectionState();
      },
      fail: (err) => {
        console.error('蓝牙初始化失败', err);
        this.handleBLEError(err);
      }
    });
  },

  // 搜索目标设备
  startDeviceDiscovery() {
    wx.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false,
      interval: 2000,
      success: () => {
        this.data.discoveryTimer = setTimeout(() => {
          wx.stopBluetoothDevicesDiscovery();
        }, 5000);

        wx.onBluetoothDeviceFound((res) => {
          const validDevices = res.devices.filter(device => 
            device.name?.includes('目标设备')
          );
          if (validDevices.length > 0) {
            const target = validDevices[0];
            this.setData({ deviceId: target.deviceId });
            this.connectBLEDevice(target.deviceId);
            wx.stopBluetoothDevicesDiscovery();
            clearTimeout(this.data.discoveryTimer);
          }
        });
      },
      fail: (err) => console.error('搜索设备失败', err)
    });
  },

  // 建立BLE连接
  connectBLEDevice(deviceId) {
    wx.createBLEConnection({
      deviceId,
      timeout: 8000,
      success: () => {
        console.log('连接成功');
        this.setData({ connected: true, retryCount: 0 });
        this.getBLEDeviceServices(deviceId);
        this.startHeartbeat();
      },
      fail: (err) => {
        console.error('连接失败', err);
        this.handleBLEError(err);
      }
    });
  },

  // 监听连接状态变化
  listenConnectionState() {
    wx.onBLEConnectionStateChange((res) => {
      this.setData({ connected: res.connected });
      if (!res.connected && this.data.shouldReconnect && this.data.deviceId) {
        this.autoReconnect();
      }
    });
  },

  // 心跳检测
  startHeartbeat() {
    this.stopAllTimers();
    this.data.heartbeatTimer = setInterval(() => {
      if (!this.data.connected) return;
      this.writeBLEHeartbeat().catch(() => {
        this.setData({ connected: false });
        this.autoReconnect();
      });
    }, 30000);
  },

  // 发送心跳指令
  writeBLEHeartbeat() {
    const { deviceId, serviceId, writeCharId } = this.data;
    return new Promise((resolve, reject) => {
      wx.writeBLECharacteristicValue({
        deviceId,
        serviceId,
        characteristicId: writeCharId,
        value: new Uint8Array([0x01]).buffer,
        success: resolve,
        fail: reject
      });
    });
  },

  // 自动重连
  autoReconnect() {
    const { deviceId, retryCount, maxRetry } = this.data;
    if (retryCount >= maxRetry) {
      this.setData({ shouldReconnect: false });
      wx.showToast({ title: `重连失败,请重试`, icon: 'none' });
      wx.showModal({
        title: '连接失败',
        content: '请检查设备状态后重试',
        confirmText: '重新连接',
        success: (res) => {
          if (res.confirm) {
            this.setData({ retryCount: 0, shouldReconnect: true });
            this.autoReconnect();
          }
        }
      });
      return;
    }

    const delay = Math.pow(2, retryCount) * 1000;
    setTimeout(() => {
      wx.createBLEConnection({
        deviceId,
        timeout: 8000,
        success: () => {
          this.setData({ connected: true, retryCount: 0 });
          this.getBLEDeviceServices(deviceId);
          this.startHeartbeat();
        },
        fail: () => {
          this.setData({ retryCount: retryCount + 1 });
          this.autoReconnect();
        }
      });
    }, delay);
  },

  // 错误统一处理
  handleBLEError(err) {
    const errCode = err.errno || err.errCode;
    const errorMap = {
      10001: { msg: '蓝牙未开启', action: 'open' },
      10006: { msg: '连接已断开', action: 'reconnect' },
      1509001: { msg: '连接失败', action: 'reconnect' },
      1500105: { msg: '系统不支持BLE', action: 'tips' }
    };

    const strategy = errorMap[errCode] || { msg: err.errMsg, action: 'none' };
    switch (strategy.action) {
      case 'open':
        wx.showModal({
          title: '提示',
          content: '请开启蓝牙后重试',
          success: (res) => res.confirm && wx.openBluetoothAdapter()
        });
        break;
      case 'reconnect':
        this.data.shouldReconnect && this.data.deviceId && this.autoReconnect();
        break;
      case 'tips':
        wx.showToast({ title: strategy.msg, icon: 'none' });
        break;
    }
  },

  // 检查连接状态
  checkConnection() {
    const { deviceId } = this.data;
    wx.getBLEDeviceConnectionState({
      deviceId,
      success: (res) => {
        if (!res.connected) this.autoReconnect();
        else {
          this.setData({ connected: true });
          this.startHeartbeat();
        }
      },
      fail: () => this.autoReconnect()
    });
  },

  // 清除所有定时器
  stopAllTimers() {
    if (this.data.heartbeatTimer) clearInterval(this.data.heartbeatTimer);
    if (this.data.discoveryTimer) clearTimeout(this.data.discoveryTimer);
  },

  // 关闭BLE连接
  closeBLEConnection() {
    const { deviceId } = this.data;
    if (deviceId) {
      wx.closeBLEConnection({ deviceId });
    }
  },

  // 获取设备服务和特征值(连接成功后调用,根据实际需求调整)
  getBLEDeviceServices(deviceId) {
    wx.getBLEDeviceServices({
      deviceId,
      success: (res) => {
        console.log('设备服务列表', res.services);
        // 后续可获取特征值,开启notify等操作
      }
    });
  }
})```

## 六、总结

微信小程序BLE异常断开检测的核心是「被动监听+主动检测」结合:通过wx.onBLEConnectionStateChange捕获状态变化,通过错误码精准定位断开原因,通过心跳检测弥补隐性断开漏洞;自动重连采用指数退避策略,配合页面生命周期联动,既能保证重连效率,又能避免资源浪费。
实际开发中,需根据设备协议调整心跳指令、serviceUUID、特征值UUID等参数,同时针对iOS/Android平台差异做适配,可有效将断联率控制在个位数,提升用户体验。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐