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

如何在使用http请求上传文件时实时显示进度?

实现Flutter http.MultipartRequest上传实时进度显示

核心思路

要实现上传进度显示,核心是拦截MultipartRequest发送的字节流,统计已发送字节数并与总字节数对比,同时结合单文件大小判断当前上传到第几张图片,最终同步到UI。

步骤1:计算总上传字节与单文件大小

先在选择图片的方法中,统计所有上传内容的总字节数(含表单字段、边界符、压缩后图片),同时记录每张图片的字节大小,用于后续判断上传进度对应的图片索引。

修改后的selectImageFromDevice方法:

// 新增全局/状态变量保存进度相关数据
int _totalUploadBytes = 0;
List<int> _singleFileSizes = [];
int _sentBytes = 0;
int _uploadedImageCount = 0;

Future<void> selectImageFromDevice() async {
  final List<XFile> listImageSelected =
      await ImagePicker().pickMultiImage(imageQuality: 40);
  if (listImageSelected.isEmpty) return;

  var postUri = Uri.parse("https://myserver");
  var request = http.MultipartRequest("POST", postUri);
  Map<String, String> header = {"Content-Type": "multipart/form-data"};

  request.fields['ID'] = widget.ct.ID;
  _singleFileSizes.clear();
  _totalUploadBytes = 0;
  _sentBytes = 0;
  _uploadedImageCount = 0;

  // 遍历图片,计算大小并添加到请求
  for (var i in listImageSelected) {
    originalImage = File(i.path);
    Uint8List? compressedImage = await FlutterImageCompress.compressWithFile(
      originalImage!.path,
      quality: 40,
    );
    if (compressedImage == null) continue;

    String fileName = i.name;
    _singleFileSizes.add(compressedImage.length);
    _totalUploadBytes += compressedImage.length;

    request.fields['FileName'] = fileName;
    request.files.add(
      http.MultipartFile.fromBytes('FileContent', compressedImage, filename: fileName),
    );
  }

  // 计算表单字段和边界符的字节大小(近似值,保证进度计算准确)
  final boundary = request.boundary;
  for (var entry in request.fields.entries) {
    _totalUploadBytes += '--$boundary\r\nContent-Disposition: form-data; name="${entry.key}"\r\n\r\n${entry.value}\r\n'.length;
  }
  _totalUploadBytes += '--$boundary--\r\n'.length;

  request.headers.addAll(header);
  await updateImage(request);
}

步骤2:拦截流统计上传进度

修改updateImage方法,通过包装发送流,实时统计已发送字节数,并计算当前上传的图片索引:

Future<void> updateImage(http.MultipartRequest request) async {
  var streamedResponse, response;
  bool isError = false;
  int accumulatedFileSize = 0;

  try {
    // 获取原始发送流并包装,添加进度监听
    final originalStream = request.finalize();
    final progressStream = originalStream.transform(StreamTransformer.fromHandlers(
      handleData: (data, sink) {
        _sentBytes += data.length;

        // 判断当前上传到第几张图片
        while (_uploadedImageCount < _singleFileSizes.length) {
          accumulatedFileSize += _singleFileSizes[_uploadedImageCount];
          if (_sentBytes <= accumulatedFileSize) break;
          _uploadedImageCount++;
        }

        // 更新UI,这里用setState示例,可替换为Provider/Riverpod等状态管理
        setState(() {
          final progressPercent = _totalUploadBytes > 0 ? (_sentBytes / _totalUploadBytes * 100).round() : 0;
          // 这里可将进度数据传递给UI组件
          print("已上传${_uploadedImageCount + 1}/${_singleFileSizes.length}张,进度${progressPercent}%");
        });

        sink.add(data);
      },
    ));

    // 使用包装后的流发送请求
    streamedResponse = await http.Client().send(
      http.StreamedRequest(request.method, request.url)
        ..headers.addAll(request.headers)
        ..contentLength = _totalUploadBytes
        ..sink.addStream(progressStream)
        ..sink.close(),
    );

    response = await http.Response.fromStream(streamedResponse);
  } catch (err) {
    isError = true;
    continuosly = false;
    ErrorMessageDialog(context, err.toString(), () {});
  }

  if (!isError) {
    if (response.statusCode == 200) {
      WebAPIResponse status = WebAPIResponse.fromJson(jsonDecode(response.body));
      if (status.Status == '0') {
        continuosly = true;
      } else {
        continuosly = false;
        ErrorMessageDialog(context, "Lỗi kết nối!", () {});
      }
    } else {
      continuosly = false;
      ErrorMessageDialog(context, "Lỗi kết nối!", () {});
    }
  }
}

步骤3:UI层展示进度

在Widget的build方法中添加进度显示组件,示例如下:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text("已上传${_uploadedImageCount + 1}/${_singleFileSizes.length}张"),
        Padding(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: LinearProgressIndicator(
            value: _totalUploadBytes > 0 ? _sentBytes / _totalUploadBytes : 0,
          ),
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: selectImageFromDevice,
          child: Text("选择图片上传"),
        ),
      ],
    ),
  );
}

关键说明

  • 总字节数计算:由于http.MultipartRequest不会自动计算总内容长度,需要手动估算表单字段、边界符和文件的字节大小,确保进度百分比准确。
  • 流拦截逻辑:通过StreamTransformer拦截每一段发送的字节,累加已发送数量,同时对比单文件大小判断当前上传的图片索引。
  • 状态管理:示例用setState更新UI,实际项目建议用Provider、Riverpod等状态管理方案,避免频繁重绘问题。

内容的提问来源于stack exchange,提问作者Dat Minh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 23:20:37