使用Flutter_Blue_Plus如何避免蓝牙流监听器重复处理设备?
解决FlutterBluePlus扫描结果流重复触发的问题
针对你遇到的scanResults流每次返回全量列表导致监听器重复调用的问题,可以通过以下关键调整解决:
- 立即终止流触发:找到目标设备后立刻取消
scanResults的订阅并停止扫描,切断后续的流发射。 - 状态标记防重复处理:用布尔变量标记是否已处理目标设备,后续流触发时直接跳过逻辑。
- 避免嵌套订阅:不要在
isScanning监听器内部嵌套订阅scanResults,这种写法容易导致重复订阅。 - 优化设备匹配:直接定位目标设备,无需每次遍历全量列表做重复判断。
修改后的代码示例
class ProgrammingStatusBloc extends Bloc<ProgrammingStatusEvent, ProgrammingStatusState> { List<StreamSubscription> streamSubList = []; bool _continueProcessing = true; bool _targetDeviceHandled = false; // 标记是否已处理目标设备 ProgrammingStatusBloc() : super(ProgrammingStatusState.initial()) { on<_Started>((event, emit) async { _continueProcessing = true; _targetDeviceHandled = false; emit(ProgrammingStatusState.initial()); // 直接启动扫描 await FlutterBluePlus.instance.startScan(); // 订阅扫描结果流 final scanSubscription = FlutterBluePlus.instance.scanResults.listen((results) async { if (!_continueProcessing || _targetDeviceHandled) return; // 快速定位目标设备 final targetResult = results.firstWhere( (r) => r.advertisementData.manufacturerData.containsKey(1234) && !await FlutterBluePlus.instance.connectedDevices.contains(r.device), orElse: () => null, ); if (targetResult != null) { final mapOfBytes = targetResult.advertisementData.manufacturerData; final listOfBytes = mapOfBytes.entries.first.value; final incomingPartialHex = BitManipulation.listOfBytesToHex(listOfBytes); final hexId = event.sensor.id.getOrCrash(); final idHexString = hexId.toRadixString(16).toUpperCase(); if (incomingPartialHex.contains(idHexString)) { _targetDeviceHandled = true; _continueProcessing = false; // 立即取消扫描订阅+停止扫描 scanSubscription.cancel(); await FlutterBluePlus.instance.stopScan(); // 处理设备连接与数据写入 try { await targetResult.device.connect(autoConnect: false); add(ProgrammingStatusEvent.connected(true)); await targetResult.device.requestMtu(512); await targetResult.device.discoverServices(); final serviceSubscription = targetResult.device.services.listen((services) async { for (final s in services) { if (s.uuid.toString() == "6e400001-b5a3-f393-e0a9-e50e24dcca9e") { final txChar = s.characteristics.firstWhere( (c) => c.uuid.toString() == "6e400002-b5a3-f393-e0a9-e50e24dcca9e", orElse: () => null, ); if (txChar != null) { final payload = createPayload(event.data); await txChar.write(payload); log("数据写入完成"); // 完成后断开连接并清理订阅 await targetResult.device.disconnect(); serviceSubscription.cancel(); break; } } } }, onError: (error) { log("设备服务监听错误: $error"); serviceSubscription.cancel(); }); streamSubList.add(serviceSubscription); } catch (e) { log("设备处理失败: $e"); // 可在此添加异常后的UI状态通知逻辑 } } } }); streamSubList.add(scanSubscription); // 超时处理:5秒未找到设备则停止扫描 await Future.delayed(const Duration(seconds: 5)); if (_continueProcessing) { scanSubscription.cancel(); await FlutterBluePlus.instance.stopScan(); log("扫描超时,未找到目标设备"); } }); } // Bloc销毁时清理所有订阅,防止内存泄漏 @override Future<void> close() { for (var sub in streamSubList) { sub.cancel(); } return super.close(); } }
关键修改说明
- 移除嵌套订阅:直接启动扫描后订阅
scanResults,避免重复创建订阅导致的多次触发。 - 提前终止逻辑:通过
_targetDeviceHandled标记设备是否已处理,后续流触发时直接返回,不再执行冗余代码。 - 立即切断流:找到目标设备后立刻取消
scanResults订阅并停止扫描,彻底阻止后续的流发射。 - 优化查找效率:用
firstWhere直接定位目标设备,替代全量遍历的低效写法。 - 异常防护:给连接、服务发现等操作添加try-catch,避免异常导致流程卡住。
- 订阅清理:重写
close方法,确保所有StreamSubscription在Bloc销毁时被取消,避免内存泄漏。
内容的提问来源于stack exchange,提问作者TarHalda
相关产品推荐
相关产品推荐

