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

Flutter Web加载后端PDF字节流预览下载报FormatException问题

Flutter 后端PDF预览、下载报错修复方案

核心报错原因

报错本质是二进制PDF流被错误做了UTF-8文本解码,导致文件结构损坏,问题来源有两个:

  1. 请求头配置错误:GET请求没有请求体,不需要设置content-type,额外加的Charset: utf-8会触发http库的文本解码逻辑,把二进制PDF当文本解析
  2. 方法调用错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 03:15:43