Android API 30应用卸载重装后无法访问公共文件夹原有文件如何解决?
适配Android API 30+公共目录文件读写解决方案
1 ACTION_OPEN_DOCUMENT_TREE返回URI的特殊要求
你授权失效绝大多数情况是遗漏了以下规则:
- 必须申请持久化权限:拿到授权返回的URI后,需要主动调用
ContentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION),否则权限会在系统重启或应用进程杀死后失效。启动授权的Intent也必须同时添加FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION两个flag。 - 无法转成绝对路径使用:该URI为
content://开头的媒体风格URI,Android 10及以上系统禁止直接将其转换为本地绝对路径访问,这是你当前传入绝对路径给库失效的核心原因。 - 权限范围有限:仅能访问用户选中的目录及其子目录内容,若你的.db文件存放在Documents根目录,必须让用户选中Documents根目录授权,选中子目录无法访问根目录文件。
- 权限绑定应用身份:卸载重装后系统会自动清除对应应用的所有持久化URI权限,必须重新引导用户授权才能访问之前创建的文件,这是系统安全机制无法绕过。
2 SqliteImportExporter库的兼容方案
你不需要替换原有库,两种改造方案可选:
- 低改造成本方案(无需修改库源码):将公共目录下的.db文件通过URI流先拷贝到应用私有目录(
context.getDatabasePath()或context.getFilesDir()路径下),将私有目录的绝对路径传给原有库完成导入、导出、修改等所有操作,操作完成后再将更新后的.db文件通过流写回公共目录的对应URI即可。 - 长期适配方案(修改库源码):扩展库的入参支持接收Uri类型,内部读写逻辑替换为
ContentResolver.openInputStream()/openOutputStream()实现,不需要再依赖绝对路径。
3 全版本兼容双向拷贝实现(支持API 21+)
3.1 清单配置
<!-- API 28及以下存储权限,API 29+用SAF不需要申请存储权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
3.2 版本分支逻辑
- API ≤ 28:直接使用原有绝对路径逻辑读写公共Documents目录,不需要调用SAF授权。
- API ≥ 29:走SAF授权流程,拿到持久化URI后通过流的方式完成文件读写。
3.3 核心流拷贝逻辑
导入(公共目录→私有目录)和导出(私有目录→公共目录)都可以复用该逻辑:
// 流拷贝通用方法 fun copyStream(input: InputStream, output: OutputStream) { val buffer = ByteArray(1024 * 8) var length: Int while (input.read(buffer).also { length = it } > 0) { output.write(buffer, 0, length) } output.flush() input.close() output.close() }
- 导入:调用
contentResolver.openInputStream(公共文件Uri)获取输入流,传入私有目录的FileOutputStream完成拷贝。 - 导出:调用
contentResolver.openOutputStream(公共文件Uri, "wt")获取输出流(wt参数会覆盖原有文件),传入私有目录的FileInputStream完成拷贝。
内容的提问来源于stack exchange,提问作者grolschie
相关产品推荐
相关产品推荐

