如何在使用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
相关产品推荐
相关产品推荐

