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

Flutter解密下载文件时遇Permission denied(errno=13)权限问题求助

问题排查与解决方案

核心问题分析

报错Permission denied, errno = 13本质是运行时未实际获取到文件读写权限——虽然你在AndroidManifest.xml中声明了权限,但Android 6.0(API 23)及以上系统要求危险权限必须动态申请,仅清单声明不足以生效。另外你的下载逻辑用固定延迟等待文件写入,存在文件未完全保存就执行解密的潜在风险,也可能触发类似报错。

分步解决

1. 动态申请文件读写权限

使用permission_handler包处理动态权限请求:

  • 先在pubspec.yaml添加依赖:
dependencies:
  permission_handler: ^10.2.0
  • 在执行下载和解密前,先完成权限校验:
import 'package:permission_handler/permission_handler.dart';

// 新增权限检查函数
Future<bool> checkAndRequestStoragePermissions() async {
  var status = await Permission.storage.status;
  if (!status.isGranted) {
    final result = await Permission.storage.request();
    return result.isGranted;
  }
  return true;
}

// 修改按钮点击逻辑
onPressed: () async {
  print("Checking permissions...");
  bool hasPermission = await checkAndRequestStoragePermissions();
  if (!hasPermission) {
    print("Storage permission denied");
    return;
  }
  print("Downloading file from drive...");
  await downloadFromDrive("ideas", fileId.toString());
  print("Decrypting the file downloaded from drive...");
  await decryptJsonFile(
      srcPath: '/storage/emulated/0/Download/test_gdrive_download.txt.aes',
      destPath: '/storage/emulated/0/Download/decrypted_file.txt');
},

2. 修复下载逻辑的异步等待问题

替换不可靠的固定延迟,用Completer监听文件流写入完成:

Future<void> downloadFromDrive(String fName, String gdID) async {
  var client = GoogleAuthClient(authHeaders);
  var ga = drive.DriveApi(client);
  drive.Media file = await ga.files
      .get(gdID, downloadOptions: drive.DownloadOptions.fullMedia) as drive.Media;

  final saveFile = File('/storage/emulated/0/Download/test_gdrive_download.txt.aes');
  List<int> dataStore = [];
  Completer<void> completer = Completer<void>();

  file.stream.listen((data) {
    print("DataReceived: ${data.length}");
    dataStore.addAll(data); // 用addAll替代insertAll,更高效
  }, onDone: () async {
    print("Task Done");
    await saveFile.writeAsBytes(dataStore);
    print("File saved at ${saveFile.path}");
    completer.complete();
  }, onError: (error) {
    print("Some Error: $error");
    completer.completeError(error);
  });

  return completer.future; // 等待文件流真正写入完成
}

3. 适配Android 11+的存储权限(可选)

若应用目标SDK为30+(Android 11),requestLegacyExternalStorage仅对Android 10生效,需额外处理:

  • 若必须访问公共Download目录,添加MANAGE_EXTERNAL_STORAGE权限(需在Google Play审核时说明用途):
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  • 更新权限检查逻辑:
import 'package:device_info_plus/device_info_plus.dart';

Future<bool> checkAndRequestStoragePermissions() async {
  if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    if (androidInfo.version.sdkInt >= 30) {
      var status = await Permission.manageExternalStorage.status;
      if (!status.isGranted) {
        final result = await Permission.manageExternalStorage.request();
        return result.isGranted;
      }
    } else {
      var status = await Permission.storage.status;
      if (!status.isGranted) {
        final result = await Permission.storage.request();
        return result.isGranted;
      }
    }
  }
  return true;
}

4. 前置验证文件状态

解密前先确认源文件存在且可读取:

decryptJsonFile({required String srcPath, required String destPath}) async {
  File srcFile = File(srcPath);
  if (!await srcFile.exists()) {
    throw Exception("Source file does not exist");
  }
  if (!await srcFile.readable()) {
    throw Exception("No permission to read source file");
  }
  crypt.decryptFileSync(srcPath, destPath);
}

总结

优先解决动态权限申请和下载逻辑的异步等待问题,这是触发当前报错的核心原因。针对更高版本Android系统,再按需适配存储权限策略。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 06:09:50