如何捕获Flutter蓝牙状态监听器中抛出的未处理异常
问题根因
你使用的Stream.onError方法只能捕获Stream自身主动发出的错误事件,而你在listen的事件回调函数内部手动抛出的异常,属于回调执行过程中产生的同步异常,不属于Stream的错误事件范畴,所以不会被onError捕获,自然就变成了未处理异常。
解决方案
方案1:在监听器回调内部包裹try-catch
直接在业务逻辑外层加try-catch,所有内部抛出的异常都能被直接捕获:
bool wasCo = false; bluetoothDevice.state.listen((event) async { try { if (event == BluetoothDeviceState.connected || event == BluetoothDeviceState.connecting) wasCo = true; if (wasCo && event == BluetoothDeviceState.disconnected) { print("We throw RefusedAssociationException"); wasCo = false; throw RefusedAssociationException(); } } catch (e) { // 在这里处理抛出的异常 print("HANDLE ERROR IS : $e"); } }); await bluetoothDevice.connect();
方案2:用asyncMap把异常转为Stream错误事件
如果希望统一走onError处理所有异常,可以把业务逻辑放到asyncMap中,asyncMap内抛出的异常会自动转为Stream的错误事件,就能被onError捕获:
bool wasCo = false; bluetoothDevice.state.asyncMap((event) async { if (event == BluetoothDeviceState.connected || event == BluetoothDeviceState.connecting) wasCo = true; if (wasCo && event == BluetoothDeviceState.disconnected) { print("We throw RefusedAssociationException"); wasCo = false; throw RefusedAssociationException(); } return event; }).listen((event) { // 正常事件处理逻辑 }).onError((handleError) => print("HANDLE ERROR IS : $handleError")); await bluetoothDevice.connect();
这类场景的最佳实践
- 不要在Stream的listen回调里直接抛出异常:流的事件回调是异步触发的,抛出的异常很难被外层逻辑捕获,优先在回调内部处理异常,或者把异常转为流的错误事件统一处理。
- 蓝牙连接场景建议用Completer管理连接状态:可以声明一个
Completer<void>管理连接结果,监听到连接成功就complete,监听到拒绝配对的断开就completeError,外层直接await这个Completer的future就能用try-catch捕获异常,逻辑更清晰,示例如下:
import 'dart:async'; final connectCompleter = Completer<void>(); bool wasCo = false; final stateSubscription = bluetoothDevice.state.listen((event) { if (event == BluetoothDeviceState.connected) { if (!connectCompleter.isCompleted) connectCompleter.complete(); } if (event == BluetoothDeviceState.connected || event == BluetoothDeviceState.connecting) { wasCo = true; } if (wasCo && event == BluetoothDeviceState.disconnected) { wasCo = false; if (!connectCompleter.isCompleted) { connectCompleter.completeError(RefusedAssociationException()); } } }); try { await bluetoothDevice.connect(); // 加超时避免无限等待 await connectCompleter.future.timeout(const Duration(seconds: 10)); } catch (e) { // 统一捕获连接超时、配对拒绝、连接失败等所有异常 print("连接失败:$e"); } finally { // 用完取消监听避免内存泄漏 stateSubscription.cancel(); }
- 用完Stream监听必须主动取消:蓝牙状态监听属于长期订阅,页面销毁或者连接结束后一定要调用
subscription.cancel(),否则会造成内存泄漏,还可能收到无用的回调触发逻辑异常。
内容的提问来源于stack exchange,提问作者Ilyes YHY
相关产品推荐
相关产品推荐

