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

Android真机遇FileNotFoundException权限拒绝,Flutter PDF下载预览失败求助

解决Flutter使用flutter_pdfview预览API下载PDF的权限问题

嘿,我看你在Android 10(API 29)环境下用flutter_pdfview加载PDF时遇到了权限和文件访问的问题,这在API 29+的存储权限变更后确实很常见,咱们一步步来解决:

1. 先补上动态权限申请的关键步骤

Android 6.0+(API 23)之后,就算你在Manifest里声明了存储权限,也必须在运行时动态请求用户授权。你目前只在配置文件里加了权限,但没在代码里触发请求,这是导致EACCES错误的核心原因之一。

  • 首先在pubspec.yaml中添加权限处理依赖:
dependencies:
  permission_handler: ^10.2.0 # 建议使用最新稳定版本
  • 然后修改预览页面的initState方法,先请求权限再下载文件:
@override
void initState() {
  super.initState();
  _requestStoragePermission().then((isGranted) {
    if (isGranted) {
      widget.model.getFile(widget.id).then((value) {
        setState(() {
          urlPDFPath = value.path;
          print("PDF文件路径: $urlPDFPath");
          // 额外检查文件是否成功写入
          File(urlPDFPath).exists().then((exists) => print("文件是否存在: $exists"));
        });
      });
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("需要存储权限才能预览PDF文件")),
      );
    }
  });
}

Future<bool> _requestStoragePermission() async {
  final status = await Permission.storage.request();
  return status.isGranted;
}

2. 修正文件下载逻辑的错误

你当前的代码是先写入文件再判断API响应状态码,这会导致即使API返回错误(比如404、500),也会生成一个空的PDF文件,后续加载必然报错。调整逻辑顺序:

Future<File> getFile(String id) async { 
  _isLoading = true; 
  notifyListeners(); 
  var headers = { 
    'Authorization': 'JWT ${_authenticatedUser.token}', 
    'Content-Type': 'application/x-www-form-urlencoded', 
    'Accept': 'application/json', 
  }; 
  try { 
    // 注意:新版本Flutter要求http.get的url必须是Uri类型
    final http.Response response = await http.get( 
      Uri.parse('api-host/pdf'), 
      headers: headers); 

    // 先判断响应状态码,再处理文件写入
    if (response.statusCode != 200) { 
      _isLoading = false; 
      notifyListeners(); 
      throw Exception('请求失败: 状态码=${response.statusCode}'); 
    }

    var bytes = response.bodyBytes; 
    // 推荐使用Option2:应用私有目录,无需外部存储权限,更安全
    var dir = await getApplicationDocumentsDirectory(); 
    // 加上id后缀避免重复文件名被覆盖
    File file = File("${dir.path}/mypdf_${id}.pdf"); 
    File urlFile = await file.writeAsBytes(bytes); 

    _isLoading = false; 
    notifyListeners(); 
    return urlFile; 
  } catch (e) { 
    _isLoading = false; 
    notifyListeners(); 
    throw Exception("下载文件出错: ${e.toString()}"); 
  } 
}

3. 检查AndroidManifest配置细节

你已经添加了android:requestLegacyExternalStorage="true",但要确保这些配置的位置正确:

  • android:requestLegacyExternalStorage="true"必须放在<application>标签内,而非<activity>标签
  • 如果你的API地址是HTTP协议(非HTTPS),需要在<application>标签内额外添加android:usesCleartextTraffic="true",否则Android 9+会拦截HTTP请求
  • 权限声明无需修改,但注意WRITE_EXTERNAL_STORAGE在Android 10+已被废弃,不过因为你启用了legacy模式,暂时可以保留

4. 理解Option1和Option2的路径差异

  • Option1(getExternalStorageDirectory):这是外部存储的应用专属目录,Android 10+即使启用legacy模式,访问该目录仍需动态权限,且flutter_pdfview可能因系统权限限制无法读取,不推荐使用。
  • Option2(getApplicationDocumentsDirectory):这是应用的内部私有目录,只有你的应用能访问,无需WRITE_EXTERNAL_STORAGE权限,安全性更高。你之前找不到文件是因为该目录默认隐藏,需在文件管理器中开启「显示隐藏文件」才能看到,或通过adb命令查看:
adb shell run-as com.your.package.name ls /data/data/com.your.package.name/files/

最后验证

修改完成后重新运行App:

  1. 首次打开预览页面会弹出权限请求,允许后再下载PDF
  2. 查看控制台日志,确认文件路径和文件存在状态
  3. 如果仍有问题,确保flutter_pdfview使用最新稳定版本(比如flutter_pdfview: ^1.2.1)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 19:22:37