Flutter中使用await for处理StreamedResponse的断网应对方案咨询
最优处理方式:可控重试+主动中断机制
不要依赖系统自动恢复StreamedResponse的数据流——断网时数据流可能直接抛出异常、长时间卡住,且不同平台/HTTP客户端的重试逻辑不一致,完全不可控。最优方案是主动监听网络状态变化,结合可中断的下载任务,断网时停止任务,联网后手动触发重试(支持断点续传更佳)。
断网时:手动停止而非被动等待
被动等待存在诸多问题:
- 数据流可能长期占用资源却无进展,引发内存泄漏或无效资源占用
- 部分HTTP客户端会在断网后直接抛出未捕获异常,导致
await for循环崩溃 - 跨平台网络层重试策略不统一,无法保证一致的恢复效果
因此更可靠的选择是断网时主动终止当前下载任务,待网络恢复后根据需求重新发起请求(大文件需结合断点续传)。
手动停止下载任务的实现方式
1. 用CancelableOperation包装下载逻辑(依赖async包)
通过CancelableOperation将下载任务包装为可取消的操作,断网时调用cancel()终止:
import 'dart:async'; import 'dart:io'; import 'package:async/async.dart'; CancelableOperation? _downloadOp; void startDownload(String url, String savePath) { _downloadOp = CancelableOperation.fromFuture( _executeDownload(url, savePath), onCancel: () { // 可选:执行资源清理,比如关闭文件流 }, ); } Future<void> _executeDownload(String url, String savePath) async { final client = HttpClient(); try { final request = await client.getUrl(Uri.parse(url)); final response = await request.close(); final file = File(savePath); final sink = file.openWrite(); await for (final chunk in response) { await sink.add(chunk); } await sink.close(); } finally { client.close(); } } // 断网时调用此方法终止下载 void stopDownload() { _downloadOp?.cancel(); _downloadOp = null; }
2. 通过StreamSubscription取消数据流监听
若直接使用await for,可先获取数据流的订阅对象,断网时取消订阅并清理资源:
StreamSubscription<List<int>>? _streamSub; FileSink? _fileSink; HttpClient? _httpClient; void startDownload(String url, String savePath) async { _httpClient = HttpClient(); try { final request = await _httpClient!.getUrl(Uri.parse(url)); final response = await request.close(); final file = File(savePath); _fileSink = file.openWrite(); _streamSub = response.listen( (chunk) => _fileSink!.add(chunk), onError: (_) => _cleanup(), onDone: () async { await _fileSink?.close(); _cleanup(); }, ); } catch (_) { _cleanup(); } } void stopDownload() { _streamSub?.cancel(); _cleanup(); } void _cleanup() { _streamSub = null; _fileSink?.close(); _fileSink = null; _httpClient?.close(); _httpClient = null; }
3. 结合网络状态监听自动处理(以connectivity_plus为例)
监听网络状态变化,自动触发停止/重试逻辑,同时支持断点续传:
import 'package:connectivity_plus/connectivity_plus.dart'; StreamSubscription<ConnectivityResult>? _netSub; void initNetworkListener() { _netSub = Connectivity().onConnectivityChanged.listen((status) { if (status == ConnectivityResult.none) { stopDownload(); } else { // 联网后触发断点续传 resumeDownload(url, savePath); } }); } // 断点续传实现:利用HTTP Range请求头 Future<void> resumeDownload(String url, String savePath) async { final file = File(savePath); int downloadedBytes = 0; if (await file.exists()) { downloadedBytes = await file.length(); } final client = HttpClient(); try { final request = await client.getUrl(Uri.parse(url)); request.headers.add('Range', 'bytes=$downloadedBytes-'); final response = await request.close(); if (response.statusCode == 206) { // 服务器支持续传,追加写入文件 final sink = file.openWrite(mode: FileMode.append); await for (final chunk in response) { await sink.add(chunk); } await sink.close(); } else { // 不支持续传,重新下载整个文件 await _executeDownload(url, savePath); } } finally { client.close(); } }
关键注意事项
- 资源清理:停止下载时必须关闭HTTP客户端、文件写入流,避免内存泄漏
- 断点续传:大文件下载必须结合
Range请求头,否则重新下载会浪费流量 - 错误捕获:下载过程中要捕获网络异常、文件IO异常,避免程序崩溃
- 状态跟踪:用变量记录当前下载状态(下载中/已暂停/已完成),防止重复发起任务
内容的提问来源于stack exchange,提问作者May
相关产品推荐
相关产品推荐

