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

Flutter(Android)内部存储文件跨文件夹复制时权限被拒问题排查

解决Flutter中Android内部存储文件复制权限被拒的问题

首先,咱们来拆解你遇到的Permission denied (errno=13)问题,主要和Android 10及以上版本的**分区存储(Scoped Storage)**机制、权限配置以及代码细节有关,下面一步步给你解决:

1. 理解核心原因:Android 10+的存储权限限制

从Android 10(API Level 29)开始,Google引入了分区存储机制,默认情况下即使你申请了WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE,也不能直接访问公共存储目录(比如/sdcard、/sdcard/Documents)下的非应用创建文件——这是系统为保护用户隐私做的强制限制。

另外你看到的/sdcard路径是正常现象:很多Android厂商把内部存储的挂载点映射为/sdcard,真正的外置SD卡会用其他路径(比如/sdcard1),不用纠结这个路径显示。

2. 第一步:适配Manifest配置

针对Android 10(API29)

在AndroidManifest.xml的application标签里添加android:requestLegacyExternalStorage="true",暂时绕过分区存储限制,让传统存储权限生效:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.xxxx">
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  
  <application
    ...
    android:requestLegacyExternalStorage="true"> <!-- 添加这一行 -->
    ...
  </application>
</manifest>

针对Android 11+(API30及以上)

如果应用目标SDK是30+,requestLegacyExternalStorage会失效,此时要访问公共存储的所有文件,需要申请MANAGE_EXTERNAL_STORAGE特殊权限,先在Manifest里添加:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30" />

3. 修正权限申请代码

你的permission_handler权限申请逻辑需要适配不同Android版本:

  • Android 10及以下:用Permission.storage
  • Android 11+:需要用Permission.manageExternalStorage,且该权限无法通过弹窗申请,得引导用户去系统设置开启

修改后的权限申请代码:

import 'package:permission_handler/permission_handler.dart';

void _grantPermission () async {
  Permission permission;
  if (await Permission.manageExternalStorage.isSupported) {
    // Android 11+ 适配特殊权限
    permission = Permission.manageExternalStorage;
  } else {
    // Android 10及以下用传统存储权限
    permission = Permission.storage;
  }

  var status = await permission.status;
  if (status.isGranted) {
    print("权限已授予");
  } else if (status.isDenied) {
    var result = await permission.request();
    if (!result.isGranted && permission == Permission.manageExternalStorage) {
      // 引导用户去设置开启特殊权限
      await openAppSettings();
    }
  } else if (status.isPermanentlyDenied) {
    // 永久拒绝时,直接引导去设置页
    await openAppSettings();
  }
}

4. 修正文件复制的代码细节

  • 必须用await处理异步操作:file.copy()是异步方法,直接调用会导致权限未完全生效就执行复制,或操作未完成就中断
  • 避免硬编码路径:推荐用path_provider包获取标准路径,适配不同设备的路径差异

先在pubspec.yaml添加path_provider依赖,再修改复制代码:

import 'package:path_provider/path_provider.dart';
import 'dart:io';

void _saveFile() async {
  // 先检查权限状态
  Permission permission = await Permission.manageExternalStorage.isSupported 
      ? Permission.manageExternalStorage 
      : Permission.storage;
  var status = await permission.status;
  if (!status.isGranted) {
    print("权限未授予,请先申请权限");
    return;
  }

  // 源文件路径(可根据实际情况调整)
  File sourceFile = File('/sdcard/Maths paper.pdf');
  // 检查源文件是否存在
  if (!await sourceFile.exists()) {
    print("源文件不存在");
    return;
  }

  // 获取Documents目录的标准路径
  Directory? documentsDir = await getExternalStoragePublicDirectory(DirectoryType.documents);
  if (documentsDir == null) {
    print("无法获取Documents目录");
    return;
  }
  File targetFile = File('${documentsDir.path}/Maths paper.pdf');

  try {
    await sourceFile.copy(targetFile.path);
    print("文件复制成功");
  } catch (e) {
    print("复制失败:$e");
  }
}

5. 长期解决方案:适配分区存储(推荐)

上面的方法是兼容传统存储的临时方案,Google推荐的长期方案是使用**存储访问框架(SAF)**或MediaStore操作公共存储文件,无需特殊权限,更符合Android隐私规范。比如用file_picker让用户选择源文件,再复制到目标目录:

import 'package:file_picker/file_picker.dart';

void _pickAndCopyFile() async {
  // 让用户选择源文件
  FilePickerResult? result = await FilePicker.platform.pickFiles();
  if (result == null) return;
  File sourceFile = File(result.files.single.path!);

  // 获取Documents目录
  Directory? documentsDir = await getExternalStoragePublicDirectory(DirectoryType.documents);
  if (documentsDir == null) return;
  File targetFile = File('${documentsDir.path}/${result.files.single.name}');

  try {
    await sourceFile.copy(targetFile.path);
    print("文件复制成功");
  } catch (e) {
    print("复制失败:$e");
  }
}

这种方式既不需要申请特殊权限,也不受分区存储限制,是更合规的做法。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 04:07:34