You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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();
  }
}

关键修改说明

  1. 移除嵌套订阅:直接启动扫描后订阅scanResults,避免重复创建订阅导致的多次触发。
  2. 提前终止逻辑:通过_targetDeviceHandled标记设备是否已处理,后续流触发时直接返回,不再执行冗余代码。
  3. 立即切断流:找到目标设备后立刻取消scanResults订阅并停止扫描,彻底阻止后续的流发射。
  4. 优化查找效率:用firstWhere直接定位目标设备,替代全量遍历的低效写法。
  5. 异常防护:给连接、服务发现等操作添加try-catch,避免异常导致流程卡住。
  6. 订阅清理:重写close方法,确保所有StreamSubscription在Bloc销毁时被取消,避免内存泄漏。

内容的提问来源于stack exchange,提问作者TarHalda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:50:47