Flutter应用重装后读取外部文件报FileSystemException权限拒绝错误
问题根因
这个报错和Flutter框架无关,是Android系统原生的存储规则+UID分配机制导致的:
- Android系统会给每个安装的应用分配独立的Linux UID,应用创建的文件默认仅归属当前UID的进程可读写,哪怕是同包名、同签名的应用,只要UID不同就没有操作权限
- 首次安装应用时,你写入公共Documents目录的文件归属首次安装的应用UID;卸载重装后,新安装的应用会被分配全新的UID,自然无权读取之前UID创建的残留文件,直接抛出errno=13权限拒绝错误
- 你硬编码的
/storage/emulated/0/Documents/属于公共共享存储路径,在Android 10(API 29)及以上版本受分区存储规则约束,未正确申请权限、未通过系统合规接口访问时,哪怕文件真实存在也会被系统拦截 - 额外提一句:你代码里写的
{"key","value"}不是合法JSON格式,后续做解析会触发格式错误,正确写法是{"key":"value"}
修复方案
根据你的业务需求选对应方案即可:
方案1:使用应用专属外部存储(优先推荐,零权限适配)
如果你的文件不需要给其他应用共享、不需要在应用卸载后保留,完全没必要操作公共存储路径,直接用系统分配的应用专属外部存储目录即可:该目录随应用卸载自动清理,重装不存在UID冲突问题,且不需要申请任何存储权限。
- 先在
pubspec.yaml中引入官方维护的路径获取插件:
dependencies: path_provider: ^2.1.2
- 替换你代码中硬编码路径的逻辑:
// 写文件方法 void _writeFile() async { // 获取应用专属外部存储路径,无需权限 final appExtDir = await getExternalStorageDirectory(); final file = File('${appExtDir?.path}/test2.txt'); setState(() { if (file.existsSync()) { _message = 'File already exists'; _content = ''; } else { const fileContent = '{"key":"value"}'; file.writeAsStringSync(fileContent); _message = 'File created'; _content = fileContent; } }); } // 读文件方法 Future<void> _readFile() async { final appExtDir = await getExternalStorageDirectory(); final file = File('${appExtDir?.path}/test2.txt'); if (!file.existsSync()) { _message = 'File is not there!'; _content = ''; } else { _message = 'File read :'; _content = await file.readAsString(); } setState(() {}); }
方案2:需要读写公共存储、访问卸载残留文件的适配
如果业务确实需要读写公共Documents目录、操作卸载后保留的文件,按系统版本做合规适配:
- 修改
android/app/src/main/AndroidManifest.xml,添加对应权限配置:
<!-- Android 13及以上版本读取媒体文件权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- Android 12及以下版本存储权限,高版本自动失效 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- 仅文件管理类应用申请全存储权限,普通应用申请会被主流应用商店拒审 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <application android:requestLegacyExternalStorage="true" <!-- 其余原有配置保持不变 --> >
- 不要硬编码
/storage/emulated/0/开头的绝对路径操作公共文件:Android 10及以上版本访问公共目录文件,优先通过系统存储访问框架(SAF)、MediaStore接口获取文件访问授权,直接硬编码路径会被多数厂商定制ROM拦截。 - 针对重装后残留的旧文件:首次启动获取存储权限后,可通过覆盖写入的方式重置文件的UID归属,后续即可正常读写;测试阶段可以手动删除公共目录下的残留旧文件,避免历史文件权限冲突。
补充说明:你看到的报错信息中path显示为空,是系统权限拦截时主动屏蔽了真实路径信息,实际指向的就是你代码中硬编码的test2.txt文件。
内容的提问来源于stack exchange,提问作者Youssef Aly
相关产品推荐
相关产品推荐

