微信小程序BLE连接异常断开检测与自动重连实现方案
·

微信小程序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平台差异做适配,可有效将断联率控制在个位数,提升用户体验。
更多推荐


所有评论(0)