如何使用Flutter Blue Plus实现蓝牙设备自动重连/重发现?
蓝牙设备断开后自动重连问题解决方案
问题背景
需要实现自行车蓝牙设备在超出范围或手机蓝牙关闭导致断开连接后,自动触发重连/重发现机制。当前使用FlutterBluePlus实现搜索连接,但尝试的断开监听代码无效,必须重启APP才能重连。
当前搜索连接代码
searchForBikes() async { FlutterBluePlus flutterBlue = FlutterBluePlus.instance; await flutterBlue.isAvailable; flutterBlue.setLogLevel(LogLevel.critical); flutterBlue.startScan( // timeout: const Duration(seconds: 4) ); for (var device in await flutterBlue.connectedDevices) { for (Bike bike in bikes) { if (bike.bluetoothName.contains(device.name)) { RealBikeConnection(bike) .connect(device, autoConnect: true) .then((_) => setState(() {})); break; } } } Set<String> tryingToConnectWith = {}; flutterBlue.scanResults.listen((results) { for (ScanResult r in results) { if (tryingToConnectWith.contains(r.device.name)) { continue; } for (Bike bike in bikes) { if (bike.bluetoothName.contains(r.device.name)) { tryingToConnectWith.add(r.device.name); RealBikeConnection(bike) .connect(r.device, autoConnect: true) .then((_) => setState(() {})); break; } } } }); }
尝试过的无效重连代码
Future<void> connection() async { StreamSubscription<BluetoothDeviceState> subscription; List<BluetoothDevice> devices = await FlutterBluePlus.instance.connectedDevices; if (devices.isNotEmpty) { BluetoothDevice device = devices.first; subscription = device.state.listen((state) { if (state == BluetoothDeviceState.disconnected) { print("Device disconnected, restarting scan..."); searchForBikes().then((_) { print("Scan started"); }); } }); } else if (devices.isEmpty){ searchForBikes(); } }
问题分析与修复方案
- 监听范围局限:原代码只监听了初始连接的第一个设备,若有多个设备或连接后设备状态未被持续监听,会导致断开事件无法触发。
- 扫描未正确重启:
searchForBikes中启动扫描时未设置合理超时,且重复调用时未先停止之前的扫描,可能导致扫描冲突。 - 连接状态订阅未持久化:
connection函数中的订阅是局部变量,函数执行完后可能被GC回收,无法持续监听状态变化。
修复后的完整代码示例
StreamSubscription<BluetoothDeviceState>? _deviceStateSubscription; Set<String> _connectedDeviceIds = {}; // 初始化时调用,建立全局监听 void initBluetoothListener() { // 监听蓝牙适配器状态变化(比如手机蓝牙被关闭后重新打开) FlutterBluePlus.instance.state.listen((bluetoothState) { if (bluetoothState == BluetoothState.on) { // 蓝牙重新开启,触发扫描重连 _restartBikeScan(); } }); } // 重启扫描逻辑,先停止再启动 Future<void> _restartBikeScan() async { FlutterBluePlus flutterBlue = FlutterBluePlus.instance; await flutterBlue.stopScan(); await Future.delayed(const Duration(milliseconds: 500)); searchForBikes(); } // 修改后的searchForBikes,增加连接后的状态监听 searchForBikes() async { FlutterBluePlus flutterBlue = FlutterBluePlus.instance; await flutterBlue.isAvailable; flutterBlue.setLogLevel(LogLevel.critical); // 启动带超时的扫描,避免持续扫描耗电 flutterBlue.startScan(timeout: const Duration(seconds: 8)); // 处理已连接设备,确保监听每个设备的状态 List<BluetoothDevice> connectedDevices = await flutterBlue.connectedDevices; for (var device in connectedDevices) { for (Bike bike in bikes) { if (bike.bluetoothName.contains(device.name)) { _setupDeviceStateListener(device); RealBikeConnection(bike) .connect(device, autoConnect: true) .then((_) { setState(() {}); _connectedDeviceIds.add(device.id.id); }); break; } } } Set<String> tryingToConnectWith = {}; flutterBlue.scanResults.listen((results) { for (ScanResult r in results) { if (tryingToConnectWith.contains(r.device.name) || _connectedDeviceIds.contains(r.device.id.id)) { continue; } for (Bike bike in bikes) { if (bike.bluetoothName.contains(r.device.name)) { tryingToConnectWith.add(r.device.name); RealBikeConnection(bike) .connect(r.device, autoConnect: true) .then((_) { setState(() {}); _connectedDeviceIds.add(r.device.id.id); _setupDeviceStateListener(r.device); }); break; } } } }); } // 为每个设备设置状态监听,断开时触发重连 void _setupDeviceStateListener(BluetoothDevice device) { _deviceStateSubscription?.cancel(); _deviceStateSubscription = device.state.listen((state) { if (state == BluetoothDeviceState.disconnected) { print("设备 ${device.name} 已断开,启动重连扫描"); _connectedDeviceIds.remove(device.id.id); _restartBikeScan(); } }); } // 在页面dispose时取消订阅,避免内存泄漏 @override void dispose() { _deviceStateSubscription?.cancel(); FlutterBluePlus.instance.stopScan(); super.dispose(); }
关键优化点
- 全局蓝牙状态监听:监听手机蓝牙开关状态,蓝牙重新开启时自动触发扫描。
- 设备状态持久监听:为每个连接成功的设备单独设置状态订阅,确保断开事件被捕获。
- 扫描生命周期管理:重启扫描前先停止之前的扫描,避免冲突;设置扫描超时,平衡重连效率与耗电。
- 连接设备ID追踪:用设备唯一ID替代名称判断,避免同名设备混淆。
内容的提问来源于stack exchange,提问作者TimTheBeastNL
相关产品推荐
相关产品推荐

