Flutter中WebSocket断开后TimeoutException致页面回退问题求助
问题分析
核心矛盾在于:WebSocket断开时触发页面跳转,但未完成的异步命令任务仍在后台执行,后续的超时异常处理或UI操作(如隐藏loading)会导致已跳转的命令页重新回到前台。本质是异步任务与页面生命周期未同步,且未主动终止pending的请求。
解决方案
通过「主动终止pending请求」+「组件生命周期校验」双维度解决问题:
1. 改造WebSocket管理类,跟踪并终止待处理请求
在WebSocket类中维护所有待执行的命令请求,断开时主动取消订阅并结束异步任务,避免后续超时逻辑触发:
class WebSocketManager { // 其他原有成员 final List<_PendingRequest> _pendingRequests = []; // 内部类存储待处理请求信息 class _PendingRequest { final Completer<dynamic> completer; final StreamSubscription<dynamic>? subscription; final Command command; _PendingRequest({ required this.completer, required this.subscription, required this.command, }); } // 改造sendCommandAndWaitForResponse方法 Future<dynamic> sendCommandAndWaitForResponse(Command command, {Duration timeout = const Duration(seconds: 30)}) async { final completer = Completer<dynamic>(); StreamSubscription<dynamic>? subscription; subscription = _messageController.stream.listen((message) { if (message == 'disconnected') { _removePendingRequest(command.number); completer.completeError('WebSocket disconnected'); } else { final decodedMessage = jsonDecode(message); if (decodedMessage['Number'] == command.number) { _removePendingRequest(command.number); completer.complete(decodedMessage); } } }); // 将请求加入待处理列表 _pendingRequests.add(_PendingRequest( completer: completer, subscription: subscription, command: command, )); sendCommand(command); return completer.future.timeout(timeout, onTimeout: () { _removePendingRequest(command.number); subscription?.cancel(); return Future.error('WebSocket response timeout ${command.commandType}'); }); } // 移除指定编号的待处理请求 void _removePendingRequest(int commandNumber) { _pendingRequests.removeWhere((req) => req.command.number == commandNumber); } // WebSocket断开时,终止所有待处理请求 void onDisconnected() { // 遍历取消所有订阅并结束异步任务 for (var req in _pendingRequests) { req.subscription?.cancel(); if (!req.completer.isCompleted) { req.completer.completeError('WebSocket disconnected'); } } _pendingRequests.clear(); // 发送断开通知到消息流 _messageController.add('disconnected'); } }
2. 页面跳转时先终止pending请求,异步任务中校验组件状态
在命令页面的断开回调中先通知WebSocket终止请求,同时在异步任务中多次校验组件是否仍挂载,避免卸载后执行UI操作:
// WebSocket断开回调 void onWebSocketDisconnected() { logger.log("WebSocket disconnected"); // 先终止所有待处理请求 widget.websocket.onDisconnected(); Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => AuthenticationPage( websocket: widget.websocket, logger: logger, ), ), ); } // 改造命令发送方法 Future<void> _showUtility() async { logger.log("Sending 'Show Utility' command..."); if (!mounted) return; // 组件已卸载则直接返回 loadingOverlay.show(); var command = ShowUtilityCommand( number: widget.websocket.messageCounter, ); try { Map<String, dynamic> responseJson = await widget.websocket.sendCommandAndWaitForResponse(command); if (!mounted) return; // 响应返回时校验组件状态 Response response = Response.fromJson(responseJson); // 后续业务逻辑同样需先校验mounted } on TimeoutException catch (e) { logger.log('Error: Timeout - ${e.message}'); if (!mounted) return; } on String catch (e) { if (e == 'WebSocket disconnected') { logger.log('Error: WebSocket disconnected'); } else { logger.log('Error: $e'); } if (!mounted) return; } catch (e) { logger.log('Error: $e'); if (!mounted) return; } if (!mounted) return; loadingOverlay.hide(); }
关键说明
- 主动终止pending请求:WebSocket断开时遍历所有待处理请求,直接结束异步任务,避免超时逻辑触发。
- 组件状态校验:利用Flutter State类的
mounted属性,在异步任务的关键节点(显示loading、处理响应、隐藏loading)校验组件是否仍存活,避免卸载后执行UI操作导致页面异常。
内容的提问来源于stack exchange,提问作者Mattia
相关产品推荐
相关产品推荐

