Flutter Web加载后端PDF字节流预览下载报FormatException问题
Flutter 后端PDF预览、下载报错修复方案
核心报错原因
报错本质是二进制PDF流被错误做了UTF-8文本解码,导致文件结构损坏,问题来源有两个:
- 请求头配置错误:GET请求没有请求体,不需要设置
content-type,额外加的Charset: utf-8会触发http库的文本解码逻辑,把二进制PDF当文本解析 - 方法调用错误:
http.readBytes在Flutter Web环境(从报错栈的dart_sdk.js地址可判断运行环境为Web)会默认走文本解码分支,拿不到原始二进制字节,才会出现「意外扩展字节」的解码错误。
分步修复方案
1. 修复网络请求层代码
去掉错误的请求头配置,用http.get直接拿原始响应字节,不要走任何默认文本解码逻辑:
import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:get_storage/get_storage.dart'; Future<Uint8List> getFile(String fileName) async { final String _token = GetStorage().read('token'); final uri = Uri.parse(EndPoint.getReceiptFile + fileName); final headers = { 'Authorization': 'Bearer $_token', 'Accept': 'application/pdf', // 仅用Accept头告诉后端期望返回PDF格式 }; final res = await http.get(uri, headers: headers); if (res.statusCode != 200) { throw Exception('文件获取失败,状态码:${res.statusCode}'); } // 直接返回原始字节,不做任何文本转码 return res.bodyBytes; }
注意:原代码里写的
content-type: application/pdf是无效配置,这个头是用来描述请求/响应体格式的,GET请求无请求体时不需要设置,加了反而可能让后端产生误判。
2. 修复GetxController层代码
明确变量类型,避免dynamic类型隐式转换,加异常捕获防止请求失败卡死加载状态:
final Rx<Uint8List> res = Uint8List(0).obs; final RxBool fileLoading = false.obs; Future<void> getReceiptFile(String filename) async { fileLoading.value = true; try { res.value = await _walletService.getFile(filename); } catch (e) { log('文件加载失败:$e'); // 可自行加弹窗提示逻辑 } finally { fileLoading.value = false; } // 不要直接打印字节的字符串形式,打印长度判断是否正常获取即可 log('文件字节长度:${res.value.length}'); }
3. 修复UI层预览逻辑
SfPdfViewer.memory原生支持加载Uint8List格式的PDF,只要字节未损坏就能正常渲染,补全空状态和加载状态:
return Obx( () => controller.fileLoading.value ? const Center(child: CircularProgressIndicator()) : controller.res.value.isEmpty ? const Center(child: Text('文件加载失败')) : SfPdfViewer.memory( controller.res.value, onDocumentLoadFailed: (details) { log('PDF加载失败'); log('错误:${details.error}'); log('详情:${details.description}'); log('字节长度:${controller.res.value.length}'); }, key: GlobalKey<SfPdfViewerState>(), controller: PdfViewerController(), initialZoomLevel: 1, ), );
4. 附加:Web端PDF下载实现
拿到原始字节后可直接触发浏览器下载,非Web平台可结合path_provider写入本地文件实现下载:
import 'dart:html' as html; void downloadPdf(Uint8List bytes, String fileName) { final blob = html.Blob([bytes], 'application/pdf'); final url = html.Url.createObjectUrlFromBlob(blob); final anchor = html.AnchorElement(href: url) ..setAttribute('download', fileName) ..click(); html.Url.revokeObjectUrl(url); }
验证注意点
- 拿到字节后先打印前5位值,正常PDF开头固定为
[37, 80, 68, 70, 45](对应文本%PDF-),如果值不对说明字节仍被转码损坏 - 全程不要对PDF字节做任何文本编码/解码操作,二进制文件一旦经过文本转码就会损坏结构
- Flutter Web环境优先用
http.get拿res.bodyBytes,不要用http.read、http.readBytes这类会默认触发文本解码的方法
内容的提问来源于stack exchange,提问作者Turan Aktas
相关产品推荐
相关产品推荐

