微信小程序BLE断连检测与自动重连方案实践 📅 发布时间:2026/9/13 2:30:04 👁 浏览次数: 做微信小程序 BLE 开发最让人头疼的往往不是设备连不上而是“明明连得好好的过一会儿莫名其妙就断了”。我最近的项目里硬件端是一块自研蓝牙模块手机通过小程序控制设备结果在真机调试和正式环境里断连问题层出不穷——距离稍远就断、切后台回来就断、设备每次复位断一次、甚至手机蓝牙开关闪一下也会断。被这些问题折磨几周后我干脆把“连接异常断开检测”和“自动重连”做成了一套完整的方案沉淀到了工程里。这篇文章就把这套方案的设计思路和具体实现完整梳理一遍涉及断连原因分类、心跳机制设计、重连策略制定、核心代码封装以及真机调试中踩过的各种坑希望能帮到同样被 BLE 断连问题折腾的朋友。先说结论微信小程序里的 BLE 连接和原生 App 一样受系统、信号、设备端多重因素影响但没有原生 App 那么高的后台优先级。所以把“状态监听 业务心跳 自动重连状态机”三件事一起做扎实才能让连接质量达到可上线水平。下面展开聊。1. 为什么连接会断先把断连原因分类再谈方案1.1 物理环境与设备端原因BLE 的本质是低功耗蓝牙信号穿透能力比经典蓝牙弱不少。在无障碍开阔环境下BLE 的可靠连接距离大约在 10~30 米但实际场景里只要隔一堵墙、放口袋里、或者设备天线位置不合理接收信号强度RSSI就会急剧下降超过连接阈值后系统有可能直接断连。设备端也有不少隐藏炸弹。很多硬件工程师调试时很容易忽略一个问题设备固件如果存在偶发崩溃或看门狗复位蓝牙协议栈会一并重启此时手机端的连接必然断开。还有一种情况是设备只支持单连接当另一台手机尝试连接时部分从机会主动踢掉旧连接或者直接拒绝新连接。再有就是设备固件里连接参数连接间隔、从机延迟、监控超时配置过于激进比如监控超时时间设置得太短手机端稍有调度延迟就被判定为超时断开。1.2 手机系统与微信小程序平台限制手机系统层面的断连原因很常见也很让人无奈。用户手动关闭蓝牙再打开所有连接瞬间失效。手机内存紧张时系统可能回收小程序进程或蓝牙服务连接直接消失。iOS 上小程序进入后台约 5~8 秒后JS 执行环境会被挂起蓝牙操作基本停摆部分机型在后台还会主动释放 BLE 连接。Android 厂商定制系统中存在各种激进的后台清理策略比如某些品牌的省电模式会在锁屏后杀掉小程序。微信小程序本身与原生 App 相比在蓝牙能力上是受限的。小程序只在“前台活跃”状态下能稳定进行蓝牙通信这是平台限制不是代码能完全绕开的。所以后续讲到的“自动重连”策略面向的是小程序回到前台后的快速恢复而不是真正的后台保活。1.3 连接被系统静默回收的隐蔽场景这里提一个很隐蔽但又经常发生的场景小程序刚连上设备用户马上切走几秒后微信在后台对小程序做了资源回收或蓝牙会话清理。等用户回到小程序时界面还停留在“已连接”状态实际底层连接已经不在。这种情况最坑因为上层没有任何事件通知只有靠业务层心跳或页面唤起时的主动检查才能发现。所以断连检测不能只依赖系统回调必须构建独立的“心跳”机制来兜底。2. 断开检测方案设计从被动监听做到主动感知2.1 系统事件监听onBLEConnectionStateChange 的正确使用微信小程序提供了wx.onBLEConnectionStateChange接口注册后能收到连接状态变化回调。连接成功或断开时回调会给出一个connected布尔字段值为false就代表连接断开。正确用法是在每次连接成功后注册监听在断开后进行清理或重连。但需要注意几个细节wx.onBLEConnectionStateChange(function(res) { console.log(BLE 连接状态变化, res.connected); if (!res.connected) { // 在这里触发断线处理 } });第一个容易踩的坑wx.onBLEConnectionStateChange是全局监听不是每次连接都会生成独立回调所以注册一次即可不要在connect里反复注册否则断开时会触发多次处理。第二个问题是系统回调在多数情况下可靠但某些 Android 机型或特定异常场景下连接断开后并没有触发该回调。比如系统蓝牙服务崩溃、小程序进程被杀后恢复等场景回调是收不到的。因此它只能作为断线检测的第一层。另外在拿到connected false后不要立即判断为“异常断开”就去重连要先排查是否有用户主动断开操作。这一点我会在第 2.3 小节单独说。2.2 业务层心跳机制把“静默断连”逼出来系统回调不是万能的所以必须在应用层做“心跳”。心跳的核心逻辑很简单连接后每隔一段时间向设备发送一个无副作用或低副作用的指令如果在规定时间内收到设备的正常回复或者确认指令写入成功就认为连接存活如果连续多次没有收到回复就判定连接其实已经失效主动执行重连流程。心跳能兜底处理什么场景最典型的就是系统回调失灵、以及连接看似存在但数据通路已经异常的情况。有些断连是“假连接”状态——状态栏显示已连接但写数据没有响应读取也不返回这种时候心跳能及时发现问题。心跳周期的选择需要根据业务场景权衡。我自己的经验是对实时性要求不高的设备比如智能锁、传感器建议 5~10 秒发送一次轻量心跳避免给设备增加无谓功耗。对实时性要求较高的设备比如需要即时控制的设备可以缩短到 2~3 秒但设备端要做好抗频繁写入的处理。如果设备支持通知notify可以订阅一个周期上报的数据把上报当作天然心跳这样不用额外发包。这里有个关键判定策略连续 2~3 个心跳周期都没有成功收到回复或写入结果异常再判定为断线避免因为一次偶发失败就误判。在心电类、运动类设备上如果设备本身会周期性主动上报数据完全可以直接用上报事件作为心跳来源。但如果你的设备是纯被动响应型例如发送一个查询才回一个结果那就必须主动发送查询指令。2.3 手动断开与异常断开的区分一个标志位搞定自动重连绝对不能覆盖用户主动断开。比如用户在小程序里点击“断开连接”按钮然后你还在后台不断悄悄重连设备被反复连上、断开体验会很奇怪也比较耗电。我的做法是维护一个manualDisconnect标志用户手动断开时先置为true再调用wx.closeBLEConnection。系统回调返回断开时判断manualDisconnect是否为true如果是只做页面状态清理不进入自动重连。如果是异常断开manualDisconnect为false则触发自动重连流程。每次用户点击连接时把manualDisconnect重置为false。这个标志位是整条断线处理逻辑的“总开关”不做好区分后面所有重连策略都会变形。2.4 连接状态维护用状态机代替散落的判断在小程序里如果连接状态散落在各个页面很容易出现状态不同步。比如 A 页面连上了跳到 B 页面显示未连接或者断线回调触发后页面还在尝试发送指令结果报未连接错误。我建议把连接状态抽象成一个统一的状态机状态分为IDLE空闲未连接。CONNECTING正在连接中。CONNECTED已连接通信正常。RECONNECTING正在自动重连。DISCONNECTED已断开可能是手动也可能是异常。所有页面通过统一管理器读取状态当状态变化时通过回调或事件发送给页面。这样能避免很多“我这个页面怎么没收到回调”的问题。3. 自动重连策略不是简单调一次 createBLEConnection 就行3.1 重连时机与延迟退避能不能一断就重连直接回答不建议断开后立即重连。从实际经验来看系统刚刚返回断开时底层蓝牙栈可能还处于资源未释放或正在清理的状态如果这时立刻再次调用wx.createBLEConnection大概率会失败而且可能引发服务发现混乱。所以合理的做法是给重连加延迟并且用指数退避的方式增加重连间隔。我使用的参数如下重连次数延迟时间说明第 1 次1 秒设备复位或系统清理后稍等片刻再连第 2 次2 秒给底层协议栈留出恢复时间第 3 次4 秒信号恢复需要时间扩大间隔第 4 次8 秒连续失败可能设备离线放慢节奏第 5 次15 秒接近手动处理边界第 6 次及以后停止自动重连建议弹窗提示用户手动操作当然这个参数可以根据业务调整。如果你做的是门锁类设备用户希望断线后尽快恢复可以把重试次数增加到 8 次每次间隔等比增长。但要注意无限重试会消耗用户手机电量也可能让设备端一直处于被连接状态影响其他设备与其连接。3.2 重连前必须做的三件事很多人在重连时吃亏因为直接拿着上一次的deviceId就createBLEConnection结果失败。在重连前建议依次完成以下三步检查检查蓝牙适配器状态调用wx.getBluetoothAdapterState确认手机蓝牙是打开的且available为true。如果蓝牙被关闭直接引导用户打开蓝牙不要盲目重连。检查设备 ID 是否仍然有效在 iOS 上deviceId是系统生成的 UUID如果微信重新启动过一次UUID 可能发生变化。如果发现重连失败就需要执行一次主动扫描用name或localName重新获取最新的deviceId。停止扫描后再连接如果此时还在扫描设备先调用wx.stopBluetoothDevicesDiscovery。边扫边连在部分 Android 机型上会报错或导致连接超时。3.3 重连过程不能重复触发这个坑非常典型几次断开回调同时触发或者心跳线程还挂着没清理导致重连流程被并发执行多个定时器一起跑。最后表现为一个正在重连另一个也在重连甚至连接成功后又被另一个重连流程关闭。我这里的对策是引入“重连中”状态锁。在进入RECONNECTING状态时直接关闭上一次重连的定时器再创建新的。每次重连成功后清空重连计数器和定时器。在代码里这一块需要写得很谨慎。3.4 连接超时与手动取消微信小程序的wx.createBLEConnection没有显式的超时参数但在很多机型上如果连接不到设备回调可能在数秒甚至十几秒后才返回失败。用户的心智等不了那么久而且如果不控制超时自动重连的节奏会被拖坏。我的做法是每次调用wx.createBLEConnection时同时启动一个 10 秒的定时器如果在 10 秒内没有收到成功回调就主动调用wx.closeBLEConnection并判定这次连接失败进入下一次退避。需要注意的是定时器触发后如果连接成功回调晚到要忽略该回调并清理定时器。这里的核心是必须管理好每一次连接尝试的生命周期否则连接尝试和重连定时器会互相干扰。4. 核心代码实现手写一套可落地的 BLE 连接管理器4.1 全局连接管理器的框架在实际项目里我不推荐把蓝牙逻辑散落在页面里建议抽出一个独立的bleManager.js作为全局单例。它负责连接、监听、心跳、重连、状态管理页面只做 UI 展示和事件订阅。下面这份代码我基于原生微信小程序 API 实现如果你用的是 uni-app把wx替换为uni即可整体结构完全相同。// bleManager.js class BleManager { constructor() { this.deviceId ; this.isConnected false; this.manualDisconnect false; this.reconnecting false; this.reconnectTimer null; this.reconnectCount 0; this.heartbeatTimer null; this.connectTimeoutTimer null; this.status IDLE; // IDLE / CONNECTING / CONNECTED / RECONNECTING / DISCONNECTED this.listeners {}; this.heartbeatSequence 0; this.lastHeartbeatAck 0; } // 注册页面监听器 on(event, callback) { if (!this.listeners[event]) this.listeners[event] []; this.listeners[event].push(callback); } // 派发事件 emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(cb cb(data)); } } // 重置连接状态 reset() { this.deviceId ; this.isConnected false; this.manualDisconnect false; this.reconnecting false; this.reconnectCount 0; this.clearReconnectTimer(); this.clearHeartbeatTimer(); this.clearConnectTimeout(); this.status IDLE; } clearReconnectTimer() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer null; } } clearHeartbeatTimer() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer null; } } clearConnectTimeout() { if (this.connectTimeoutTimer) { clearTimeout(this.connectTimeoutTimer); this.connectTimeoutTimer null; } } }4.2 连接设备与状态监听封装连接设备时我会在wx.createBLEConnection里带上timeout参数这是微信官方支持的连接超时参数单位为毫秒。同时在连接成功后获取服务与特征值。async connectDevice(deviceId) { if (this.status CONNECTING || this.status CONNECTED) { console.warn(当前已有连接任务请勿重复连接); return; } this.deviceId deviceId; this.manualDisconnect false; this.status CONNECTING; this.emit(statusChange, { status: this.status }); await this.stopDiscovery(); try { await this.createConnection(deviceId); const { services } await wx.getBLEDeviceServices({ deviceId }); // 在这里根据 serviceId 找到目标服务 this.targetService services.find(s s.uuid.indexOf(this.SERVICE_UUID) -1); if (!this.targetService) throw new Error(未找到目标服务); const { characteristics } await wx.getBLEDeviceCharacteristics({ deviceId, serviceId: this.targetService.uuid, }); // 根据特征值 UUID 找到读写通知特征值 this.notifyCharacteristic characteristics.find(c c.uuid.indexOf(this.NOTIFY_UUID) -1); this.writeCharacteristic characteristics.find(c c.uuid.indexOf(this.WRITE_UUID) -1); if (this.notifyCharacteristic) { await wx.notifyBLECharacteristicValueChange({ deviceId, serviceId: this.targetService.uuid, characteristicId: this.notifyCharacteristic.uuid, state: true, }); } this.status CONNECTED; this.isConnected true; this.reconnectCount 0; this.emit(statusChange, { status: this.status }); this.startHeartbeat(); } catch (err) { console.error(连接失败, err); this.emit(connectError, { message: err.message || 连接失败 }); this.status IDLE; this.isConnected false; throw err; } } createConnection(deviceId) { return new Promise((resolve, reject) { // 开启连接超时控制 this.clearConnectTimeout(); this.connectTimeoutTimer setTimeout(() { wx.closeBLEConnection({ deviceId, fail: () {} }); reject(new Error(连接超时)); }, 10000); wx.createBLEConnection({ deviceId, success: (res) { this.clearConnectTimeout(); resolve(res); }, fail: (err) { this.clearConnectTimeout(); reject(err); }, }); }); } stopDiscovery() { return new Promise((resolve) { wx.stopBluetoothDevicesDiscovery({ complete: () resolve() }); }); }这里我有个习惯在createBLEConnection成功回调里使用setTimeout做超时保护。官方参数里的timeout在某些老版本微信上可能不生效自己用定时器更保险。4.3 全局断开监听与心跳实现连接成功后注册一次wx.onBLEConnectionStateChange监听放在构造函数或独立的初始化方法里执行即可避免重复注册。initStateListener() { wx.onBLEConnectionStateChange((res) { console.warn(BLE 连接状态变化, res.connected); if (!res.connected) { this.isConnected false; this.clearHeartbeatTimer(); if (this.manualDisconnect) { // 用户手动断开不重连 this.status DISCONNECTED; this.emit(statusChange, { status: this.status }); } else { // 异常断开进入自动重连流程 this.handleDisconnect(); } } }); }心跳部分我建议单独封装一个方法。每次心跳发送一个读特征值的请求或者写一个无副作用的指令。以读操作为例startHeartbeat() { this.clearHeartbeatTimer(); this.lastHeartbeatAck Date.now(); this.heartbeatTimer setInterval(() { if (!this.isConnected) { console.warn(心跳检测时已断开); this.clearHeartbeatTimer(); return; } wx.readBLECharacteristicValue({ deviceId: this.deviceId, serviceId: this.targetService.uuid, characteristicId: this.readCharacteristic.uuid, success: () { this.lastHeartbeatAck Date.now(); }, fail: (err) { console.warn(心跳失败, err); // 连续失败达到阈值时判定断开 if (Date.now() - this.lastHeartbeatAck 15000) { console.warn(心跳超时判定连接断开); this.isConnected false; this.clearHeartbeatTimer(); this.handleDisconnect(); } }, }); }, 5000); }写操作类似关键是判断写入是否返回success。有些设备对读操作有权限限制但写操作可能也会被限制所以心跳指令要提前和设备端约定好最好是“查询状态”类的指令不改变设备任何状态。还需要注册wx.onBLECharacteristicValueChange当设备主动上报数据时可以顺手刷新心跳 ack 时间把设备上报当作心跳响应initCharacteristicListener() { wx.onBLECharacteristicValueChange((res) { if (res.deviceId this.deviceId) { this.lastHeartbeatAck Date.now(); // 将数据透传给页面 this.emit(data, res.value); } }); }这里有个细节如果设备是主动推送型比如心率监测每秒上报一次那么心跳包其实可以不发直接依赖上报数据即可。但为了防止“连接在数据流断”的异常建议还是保留一个 10 秒左右的兜底超时判断。4.4 自动重连核心逻辑重连逻辑的核心点是“延迟 退避 次数限制 状态锁”。下面是我的实现handleDisconnect() { if (this.reconnecting) return; if (this.manualDisconnect) return; this.reconnecting true; this.status RECONNECTING; this.emit(statusChange, { status: this.status }); this.scheduleReconnect(); } scheduleReconnect() { if (this.reconnectCount 5) { console.warn(重连次数过多停止自动重连); this.reconnecting false; this.status DISCONNECTED; this.emit(statusChange, { status: this.status }); this.emit(reconnectFail, { message: 自动重连失败请手动连接 }); return; } this.reconnectCount; const delay this.getReconnectDelay(this.reconnectCount); console.warn(第 ${this.reconnectCount} 次重连延迟 ${delay}ms); this.clearReconnectTimer(); this.reconnectTimer setTimeout(async () { try { await this.connectDevice(this.deviceId); this.reconnecting false; this.emit(reconnectSuccess); } catch (err) { console.error(重连失败继续下一次, err); this.scheduleReconnect(); } }, delay); } getReconnectDelay(count) { const delayMap { 1: 1000, 2: 2000, 3: 4000, 4: 8000, 5: 15000, }; return delayMap[count] || 15000; }在重连过程中connectDevice内部已经做了状态判断不会重复执行。同时如果此时用户手动点击了连接按钮需要先清理reconnecting状态避免状态锁阻塞用户操作。这里有个需要注意的点当用户手动点击连接时务必调用this.clearReconnectTimer()并把reconnecting置为false否则后台挂着的定时器会在用户操作后继续执行重连造成冲突。4.5 页面生命周期配合切后台回来立刻检测微信小程序在切后台再回前台时连接状态很可能已变化。所以要在页面的onShow里主动检查连接状态。我通常在页面onShow里调用bleManager.getStatus()同时调用一次wx.getConnectedBLEDevices或直接检查isConnected标志如果发现不处于连接态则触发重连。在onHide和onUnload里除了清理页面自身的定时器还要考虑是否暂停全局心跳。我采取的策略是onHide时不主动断开连接但心跳可能会因为 JS 挂起而停掉回前台后重新启动心跳并立即检查一次状态。onShow() { bleManager.on(statusChange, this.handleStatusChange); bleManager.on(data, this.handleData); bleManager.on(reconnectFail, this.handleReconnectFail); if (bleManager.status CONNECTED || bleManager.status RECONNECTING) { // 回前台后立即触发一次状态检查 bleManager.checkConnection(); } } onHide() { bleManager.emit(pageHide); }这里的checkConnection可以简单对某一个特征值做一次读操作或者直接读取isConnected。更严谨的做法是调一次wx.getConnectedBLEDevices确认当前连接是否还在但这个方法在部分 Android ROM 上可能返回不完整所以我更依赖业务心跳来判定。5. 常见问题与排查技巧实录5.1 高频问题速查表我在开发与测试过程中整理了下面这个速查表基本能覆盖大部分断连与重连问题问题现象可能原因处理办法断开后立即重连失败底层协议栈还未释放延迟至少 1 秒再重连先closeBLEConnectionAndroid 上一断开就连接提示 10008 等错误系统蓝牙资源未清理重连前先closeBLEConnection再等待一次心跳间隔iOS 上切后台几分钟后回来连接已断小程序后台被挂起无法完全避免回前台后立即检测并重连扫描不到设备未开启定位权限或未开启蓝牙检查 Android 6.0 定位权限、系统蓝牙开关连接成功但写数据失败特征值权限不匹配调用getBLEDeviceCharacteristics检查属性确认write权限多个页面同时操作蓝牙导致状态错乱全局状态不同步全局单例管理器 状态机页面只订阅事件心跳超时误判设备上报频率低于心跳周期延长超时阈值或把设备上报当作心跳 ack重连后页面 UI 没刷新页面在 unload 后继续订阅监听在 onUnload 时注销事件监听5.2 安卓机型常见坑定位权限与扫描回调在 Android 6.0 及以上版本扫描 BLE 设备需要定位权限。如果忘记申请wx.startBluetoothDevicesDiscovery可能能调用但永远发现不了设备或者发现不全。另外部分 Android 11API 30以上机型对附近设备权限也有要求需要在系统设置中开启“附近设备”权限。扫描过程的处理也有技巧wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success() { wx.onBluetoothDeviceFound((res) { console.log(发现设备, res.devices); // 根据名称或广播数据过滤目标设备 }); }, });扫描到设备后如果存在重复上报记得用deviceId去重。另外在连接前主动调用wx.stopBluetoothDevicesDiscovery能降低部分 Android 机型连接超时的概率。5.3 苹果机型常见坑连接参数与后台限制iOS 上更常见的坑是后台挂起。微信小程序在 iOS 上进入后台后App 级别的蓝牙回调基本不会执行等回到前台时连接状态可能已经变了。所以我建议在小程序onShow里主动做一次“连接状态探测”不要盲目相信页面缓存状态。另外iOS 上deviceId不是传统意义上的 MAC 地址而是系统生成的 UUID在微信重新启动后可能变化。如果你把deviceId持久化存储在本地重连时发现失效就要触发重新扫描用设备名去匹配目标设备。5.4 调试阶段的“断连模拟”方法如果你没有现成的物理断连环境可以用下面几种方式模拟断连方便调试自动重连逻辑在微信开发者工具中手动关闭蓝牙开关。真机调试时把手机拿到远离设备的位置让信号降到临界值。使用手机系统的“开发者选项”中的“蓝牙连接超时”相关设置部分机型有。直接重启设备端开发板或模块观察小程序端是否触发重连。在onBLEConnectionStateChange回调里主动模拟一次handleDisconnect()验证重连流程会不会重复触发。5.5 重连过程中用户手动操作的处理自动重连过程中用户可能看到页面上一直显示“正在重连”以为卡住了这时候如果又去点击“连接”按钮就会产生冲突。避免冲突的办法在重连状态时连接按钮应该置灰或显示“重连中”同时提供“取消重连”按钮。用户点击“取消重连”时需要清除重连定时器。把reconnecting置为false。把manualDisconnect置为true防止系统回调再次触发重连。更新 UI 状态为未连接。5.6 上报型设备的心跳简化方案如果你的设备是持续上报型的例如心率、温度、GPS 数据其实不需要额外发送心跳包。你只需要在onBLECharacteristicValueChange回调里刷新lastHeartbeatAck然后设置一个超时定时器比如如果超过 15 秒没有收到任何设备数据就认为连接异常触发断开检测。这种方式最简单也不会增加设备端的交互复杂度。前提是设备端必须保证数据上报的稳定性不能长时间静默。6. 订阅了多个特征值时的断开检测细节实际项目中设备往往不止一个特征值。比如有些模块有独立的写特征值、通知特征值和读特征值分别对应不同功能。在做断线检测时不要把心跳绑死在某个特定特征值上因为如果该特征值本身就没有数据返回很容易误判。我的经验是优先使用“设备主动通知”的特征值作为心跳来源。如果没有通知特征值则用“读到固定版本号或状态字”的读特征值作为心跳来源。如果设备只有一个写特征值心跳可以写一个“无操作指令”例如很多自定义协议里有0x00表示空指令或查询指令。不同特征值的连接状态都是同一个物理连接所以心跳只需要选择一个通道即可不需要每个特征值都发心跳包。写指令时也要小心。有些设备对写入频率敏感如果 5 秒写一次查询指令设备端可能觉得过于频繁。最好和设备端工程师协商一个对设备无副作用的心跳指令并约定响应内容。7. 一套可复用的状态机与事件机制建议这里分享一个完整的状态机设计虽然代码层面已经部分体现但状态流转关系还是值得单独梳理出来初始态IDLE没有连接任务可以发起新连接。连接中CONNECTING正在调用createBLEConnection不能被重复连接打断。已连接CONNECTED连接成功心跳正常可以读写数据。重连中RECONNECTING检测到异常断开自动重连流程进行中。已断开DISCONNECTED手动断开或自动重连失败后的最终状态。状态流转关系IDLE - CONNECTING用户发起连接或自动重连前调用connectDevice。CONNECTING - CONNECTED连接成功。CONNECTING - IDLE连接失败没有进入重连流程比如用户手动取消。CONNECTED - RECONNECTING检测到异常断开进入自动重连。CONNECTED - DISCONNECTED用户手动断开。RECONNECTING - CONNECTED重连成功。RECONNECTING - IDLE重连周期内用户手动取消。RECONNECTING - DISCONNECTED达到最大重连次数停止自动重连。这个状态机一定要在全局唯一实现所有页面和组件通过订阅statusChange事件来更新 UI。这样即便有多个页面引用同一个连接也不会出现“页面 A 显示已连接页面 B 显示未连接”的冲突。在实际工程中我通常还会维护一个lastError字段在连接失败、断线、心跳超时等情况下写入错误信息供技术排查和用户提示使用。用户看到“连接断开正在自动重连”和直接看到一个空白的失败提示两者体验差异非常大。8. 最后再分享一点实战体会这套断连检测与自动重连的方案在我实际项目中已经稳定运行了三个多月基本把“设备悄悄断开用户不知道”这个老大难问题解决了。但我要提醒一句自动重连不是万能的它解决的是“能恢复的服务尽量恢复”的问题而不是“所有断连都必须自动恢复”。如果设备已经断电、超出信号范围自动重连再多次也是徒劳反而会让用户觉得小程序在反复弹错误。所以在设计自动重连策略时一定要给用户保留“手动操作”的出口。当重连尝试达到上限后不要默默继续也别只给一个干巴巴的错误码而是明确提示“设备连接异常请靠近设备或检查设备电源”并提供一个“手动重连”按钮。这套交互虽然在代码里只是几行字但对真实用户体验的提升非常明显。如果后续想继续扩展可以在重连状态机里加入“设备离线去重扫描”的逻辑当重连失败后自动扫描附近设备如果发现目标设备已经更换了deviceId就静默更新设备标识并用新的 ID 重连。这样即使微信重启或系统清除了 UUID用户也无感知。这个能力对 iOS 设备尤其有用算是很值得投入的下一步优化方向。