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

Flutter如何在应用目录外的storage/emulated/0路径下创建文件夹

问题根因

Android 10(API 29)引入了分区存储机制,默认禁止应用直接访问公共存储路径,Android 11(API 30)进一步收紧了存储权限,普通权限申请无法拿到公共目录的读写权限,所以会出现低版本正常、高版本真机失效的问题。另外你原代码中Directory.create()是异步方法,未加await会导致文件夹未创建完成就执行文件拷贝,也是异常原因之一。

解决方案

方案1:兼容旧逻辑直接操作公共路径(适合非上架应用,无需调整存储逻辑)

  • 第一步:修改android/app/src/main/AndroidManifest.xml配置
    1. 在manifest标签下添加权限声明:
      <!-- 适配Android 9及以下 -->
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      <!-- 适配Android 11及以上,如需上架Google Play需额外说明使用场景 -->
      <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
      
    2. 在application标签添加兼容属性:
      <application
          ...
          android:requestLegacyExternalStorage="true">
      
  • 第二步:调整权限申请逻辑
    MANAGE_EXTERNAL_STORAGE是特殊权限,普通申请弹窗无法授予,需要引导用户手动跳转到系统设置页开启「允许访问所有文件」权限,你当前的_getPermission方法大概率没有处理这个特殊权限的跳转逻辑,导致真机上实际未拿到权限。
  • 第三步:修正核心代码
    takePhoto(BuildContext context) async {
      if (await _getPermission(Permission.manageExternalStorage)){
        final image =await picker.pickImage(source: ImageSource.camera);
        // 注意路径开头要加斜杠
        const newPath = "/storage/emulated/0/takephotosaver";
        // 加await,递归创建文件夹
        await Directory(newPath).create(recursive: true);
        File fileImage = File(image!.path);
        var imageName = DateTime.now().millisecondsSinceEpoch.toString();
        await fileImage.copy("$newPath/$imageName.jpg");
        if(context.mounted) {
          Navigator.push(context, MaterialPageRoute(builder: (context)=>MyPreview(fileImage)));
        }
      }
    }
    

方案2:使用MediaStore存储(符合谷歌规范,适合上架应用)

不需要申请敏感的全文件管理权限,直接将照片写入系统相册的独立文件夹,会自动展示在系统相册中,适配所有Android版本:

// 需提前添加依赖:path_provider、device_info_plus、media_scanner_scan_null_safe
saveImageToGallery(File imageFile, String folderName) async {
  final bytes = await imageFile.readAsBytes();
  final imageName = "${DateTime.now().millisecondsSinceEpoch}.jpg";

  if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    if (androidInfo.version.sdkInt >= 29) {
      // Android 10+ 使用MediaStore
      final contentResolver = PlatformDispatcher.instance.views.first.context.contentResolver;
      final imageUri = await contentResolver.insert(
        MediaStore.images.externalContentUri,
        ContentValues()
          ..put(MediaStore.mediaColumns.displayName, imageName)
          ..put(MediaStore.mediaColumns.mimeType, "image/jpeg")
          ..put(MediaStore.mediaColumns.relativePath, "DCIM/$folderName/")
          ..put(MediaStore.mediaColumns.isPending, 1),
      );
      final outputStream = await contentResolver.openOutputStream(imageUri!);
      await outputStream?.write(bytes);
      await contentResolver.update(
        imageUri,
        ContentValues()..put(MediaStore.mediaColumns.isPending, 0),
        null,
        null,
      );
    } else {
      // Android 9及以下直接写入公共路径
      final directory = Directory("/storage/emulated/0/DCIM/$folderName/");
      await directory.create(recursive: true);
      final file = File("${directory.path}/$imageName");
      await file.writeAsBytes(bytes);
      // 通知相册刷新
      await MediaScanner.loadImage(file.path);
    }
  }
}
常见排查点
  • 确认真机上已经授予了「允许访问所有文件」权限,可在系统设置-应用管理-你的应用-权限管理中查看
  • 路径不要拼写错误,/storage/emulated/0/开头的斜杠不能省略
  • 所有异步IO操作都要添加await等待执行完成

内容的提问来源于stack exchange,提问作者Vivek T V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:36:05