uni-app蓝牙开发避坑实录:监听事件重复触发?我用全局事件总线一招搞定

发布时间:2026/6/3 9:21:54

uni-app蓝牙开发避坑实录:监听事件重复触发?我用全局事件总线一招搞定 uni-app蓝牙开发实战全局事件总线解决监听重复触发难题第一次在uni-app里调用uni.onBluetoothDeviceFound时我天真地以为这就是全部——直到设备列表里突然出现几十个重复的蓝牙设备名称整个页面卡顿到无法操作。更可怕的是切换到其他页面后监听器仍在后台疯狂累积事件内存占用以肉眼可见的速度增长。如果你也遇到过类似问题这篇文章或许能帮你节省三天调试时间。1. 为什么蓝牙监听会变成复读机去年接手医疗设备管理项目时我们需要扫描病房里的数十台体征监测仪。测试时发现每次返回扫描页面设备列表就会重复出现——明明只有20台设备列表却显示60多个条目。更诡异的是退出页面后手机明显发烫再次进入时直接闪退。核心问题在于uni-app蓝牙API的特殊机制uni.onBluetoothDeviceFound是永久性监听没有类似Web开发中的removeEventListener每次进入页面重复调用时会叠加监听器而非替换页面卸载时不会自动销毁监听导致内存泄漏// 错误示范每次onLoad都新增监听 onLoad() { uni.onBluetoothDeviceFound(res { this.deviceList.push(res.devices[0]) }) }通过性能分析工具可以看到反复进出页面后蓝牙相关内存占用从2MB暴涨到50MB相同设备被重复触发8-10次后台持续扫描消耗电量2. 全局事件总线的降维打击经过多次尝试最终采用全局事件总线单例监听的方案。原理很简单整个App只维持一个蓝牙监听通过uni-app自带的uni.$emit/uni.$on机制分发事件。2.1 架构设计方案监听位置事件管理内存控制适用场景原生API各页面独立不可控差简单demo全局总线App级别单例集中管理优秀生产环境关键实现步骤在App.vue初始化全局监听通过uni.$emit转发蓝牙事件业务页面使用uni.$on订阅页面卸载时uni.$off取消订阅2.2 完整代码实现首先创建常量定义文件// utils/event-constants.js export const BLUETOOTH_EVENTS { DEVICE_FOUND: BLUETOOTH_DEVICE_FOUND, STATE_CHANGE: BLUETOOTH_STATE_CHANGE }接着封装蓝牙服务层// services/bluetooth-service.js import { BLUETOOTH_EVENTS } from ./event-constants let isListening false export function startBluetoothMonitoring() { if (isListening) return uni.onBluetoothDeviceFound(res { uni.$emit(BLUETOOTH_EVENTS.DEVICE_FOUND, { timestamp: Date.now(), devices: res.devices }) }) isListening true }在App.vue中初始化import { startBluetoothMonitoring } from /services/bluetooth-service export default { onLaunch() { startBluetoothMonitoring() } }业务页面使用import { BLUETOOTH_EVENTS } from /utils/event-constants export default { data() { return { devices: [] } }, onLoad() { uni.$on(BLUETOOTH_EVENTS.DEVICE_FOUND, this.handleDeviceFound) }, onUnload() { uni.$off(BLUETOOTH_EVENTS.DEVICE_FOUND, this.handleDeviceFound) }, methods: { handleDeviceFound(payload) { // 设备去重逻辑 const newDevices payload.devices.filter(device !this.devices.some(d d.deviceId device.deviceId) ) this.devices [...this.devices, ...newDevices] } } }3. 性能优化进阶技巧3.1 设备发现节流控制原始蓝牙事件触发频率可能高达每秒10次需要添加节流逻辑// 在bluetooth-service.js中添加 let lastEmitTime 0 export function startBluetoothMonitoring() { uni.onBluetoothDeviceFound(res { const now Date.now() if (now - lastEmitTime 300) return // 300ms内不重复触发 lastEmitTime now uni.$emit(BLUETOOTH_EVENTS.DEVICE_FOUND, { devices: res.devices }) }) }3.2 特征值变化监听陷阱BLE特征值监听同样存在重复触发问题解决方案// 特征值监听管理 const characteristicCallbacks new Map() export function listenCharacteristic(deviceId, serviceId, characteristicId, callback) { const key ${deviceId}-${serviceId}-${characteristicId} if (!characteristicCallbacks.has(key)) { uni.onBLECharacteristicValueChange(res { if (res.deviceId deviceId res.serviceId serviceId res.characteristicId characteristicId) { characteristicCallbacks.get(key)(res.value) } }) } characteristicCallbacks.set(key, callback) } export function stopListenCharacteristic(deviceId, serviceId, characteristicId) { const key ${deviceId}-${serviceId}-${characteristicId} characteristicCallbacks.delete(key) }4. 真实项目中的避坑指南在智能家居控制项目中我们遇到过这些特殊情况Android设备兼容性问题某些华为机型需要先调用closeBluetoothAdapter才能彻底释放资源添加以下清理逻辑// 在App.vue中 onHide() { uni.closeBluetoothAdapter() } onShow() { startBluetoothMonitoring() }iOS后台运行限制应用进入后台后系统可能暂停蓝牙扫描需要添加状态恢复机制uni.onAppShow(() { if (this.needBluetooth) { startBluetoothMonitoring() } })设备过滤策略建议在服务层就进行初步筛选减少不必要的事件传递// bluetooth-service.js中添加 const TARGET_DEVICES [HM-10, CC2541] function shouldEmit(device) { return TARGET_DEVICES.some(name device.name.includes(name) || device.localName.includes(name) ) }这套方案在智能货架项目中经受住了考验同时管理200蓝牙电子价签页面切换流畅内存稳定在15MB左右。关键在于始终保持单一数据源和明确的生命周期管理。

相关新闻