Flutter如何在应用目录外的storage/emulated/0路径下创建文件夹
问题根因
Android 10(API 29)引入了分区存储机制,默认禁止应用直接访问公共存储路径,Android 11(API 30)进一步收紧了存储权限,普通权限申请无法拿到公共目录的读写权限,所以会出现低版本正常、高版本真机失效的问题。另外你原代码中Directory.create()是异步方法,未加await会导致文件夹未创建完成就执行文件拷贝,也是异常原因之一。
解决方案
方案1:兼容旧逻辑直接操作公共路径(适合非上架应用,无需调整存储逻辑)
- 第一步:修改
android/app/src/main/AndroidManifest.xml配置- 在
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" /> - 在
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
相关产品推荐
相关产品推荐

