如何使用http包实现类似Dio的onSendProgress文件上传进度监听
在Flutter http包中实现上传进度监听
要在http包的Multipart上传中实现类似Dio的onSendProgress功能,核心思路是包装文件的ByteStream,在数据流传输时统计已发送字节数,具体实现步骤如下:
1. 实现进度跟踪的Stream包装器
先写一个工具函数,用来包装原始的文件字节流,在每段数据发送时触发进度回调:
Stream<List<int>> trackUploadProgress( Stream<List<int>> stream, int totalLength, Function(int sent, int total) onProgress, ) { int sentBytes = 0; return stream.map((chunk) { sentBytes += chunk.length; onProgress(sentBytes, totalLength); return chunk; }); }
2. 计算上传总数据量
要展示整体上传进度,需要先计算所有表单字段和文件的总大小:
- 表单字段的大小:把每个字段的key和value转成字节后(包含multipart表单的分隔符、头部信息)求和
- 文件的总大小:所有选中文件的长度之和
// 计算表单字段的总字节数(近似值) int calculateFieldsTotalSize(http.MultipartRequest request) { int total = 0; request.fields.forEach((key, value) { total += '--${request.boundary}\r\nContent-Disposition: form-data; name="$key"\r\n\r\n$value\r\n'.length; }); return total; } // 计算所有文件的总字节数 int calculateFilesTotalSize(List<File> files) { return files.fold(0, (sum, file) => sum + file.lengthSync()); }
3. 修改原上传代码,集成进度监听
在创建MultipartFile时,替换原始的ByteStream为带进度跟踪的流,同时维护全局的已发送字节数和总大小:
// 全局变量跟踪总进度 int _totalUploadSize = 0; int _sentBytes = 0; function() async{ try{ var uri = Uri.parse('MY API'); http.MultipartRequest request = http.MultipartRequest('POST', uri); request.fields['subject'] = newDemandSubjectController.text; request.fields['text'] = newDemandBodyController.text; List<http.MultipartFile> newList = <http.MultipartFile>[]; // 计算总上传大小:表单字段 + 文件总大小 + 表单结尾分隔符 int fieldsSize = calculateFieldsTotalSize(request); int filesSize = calculateFilesTotalSize(selectedImages.map((img) => File(img.path)).toList()); _totalUploadSize = fieldsSize + filesSize + '--${request.boundary}--\r\n'.length; _sentBytes = fieldsSize; // 表单字段会先被发送,初始值设为字段总大小 for(var image in selectedImages){ File imageFile = File(image.path); var stream = imageFile.openRead(); var fileLength = imageFile.lengthSync(); // 包装流,跟踪单个文件的上传进度,并累加全局进度 var progressStream = trackUploadProgress( stream, fileLength, (sent, total) { _sentBytes = fieldsSize + sent; // 这里可以触发UI更新,比如通知进度条组件 print('上传进度:${(_sentBytes / _totalUploadSize * 100).toStringAsFixed(2)}%'); print('已发送:$_sentBytes / 总大小:$_totalUploadSize'); }, ); var multipartFile = http.MultipartFile( "images[]", http.ByteStream(progressStream), fileLength, filename: '${DateTime.now()}.${image.path.split('.').last}' ); newList.add(multipartFile); } request.headers.assignAll({ 'Accept': 'application/json', 'Authorization': 'Bearer $USER_TOKEN' }); request.files.addAll(newList); var response = await request.send(); response.stream.transform(utf8.decoder).listen((value) { print(value); }); if (response.statusCode == 200) { print('上传完成'); } else { print('上传失败'); } }catch(e){ return Future.error(jsonDecode(e.toString())); } }
注意事项
- 表单字段的大小计算是近似值,实际multipart表单的分隔符、头部信息会有字节占用,这个计算能保证进度条大致准确;若需要更精确可以直接解析request的完整字节流,但复杂度会更高。
- 若要在UI中更新进度条,建议使用
ValueNotifier或状态管理工具(如Provider、Riverpod)来通知UI刷新,避免直接在回调中操作UI。
内容的提问来源于stack exchange,提问作者Aliasghar Gh
相关产品推荐
相关产品推荐

