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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:45:31