Android未立即使用ContentProvider URI触发SecurityException权限拒绝问题
你遇到的报错不是URI本身存在有效期,是系统随Intent授予的URI临时访问权限和接收该Intent的组件生命周期绑定,完全不需要在收到Intent时立刻读取文件,只要正确处理权限,完全可以延后到需要的时机再访问。
当外部文件管理器等App通过Intent向你的Activity传递content://类型URI时,系统只会给当前接收这个Intent的Activity组件授予临时URI读写权限,这个权限默认会在你离开该组件(比如跳转其他页面、原Activity被销毁/退到后台)时自动回收。你仅存储URI字符串相当于只保存了资源地址,没有同步保留访问权限,后续读取自然会抛出权限拒绝异常。
方案1:持久化URI权限(最推荐,全版本适配)
在最初接收文件打开Intent的入口Activity的onCreate/onNewIntent生命周期中,拿到intent.data后第一时间调用系统API,把临时授权转换为App持有的持久化访问权限,只要不主动释放,哪怕App重启后也能正常访问对应文件:
// 第一步:拿到URI后立刻持久化访问权限 val targetUri = intent.data!! val permissionFlags = intent.flags and ( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) contentResolver.takePersistableUriPermission(targetUri, permissionFlags) // 第二步:随便把URI转成字符串存储、跳转任意页面、延后任意时间再读都不会有权限问题 val storedUriStr = targetUri.toString() // 后续读取时把字符串转回Uri即可,逻辑和你之前的读取代码完全一致 val fileUri = Uri.parse(storedUriStr) val br = BufferedReader(InputStreamReader(contentResolver.openInputStream(fileUri)))
注意:如果确定后续不再需要访问该文件,记得调用
contentResolver.releasePersistableUriPermission()释放权限,避免占用系统的授权配额。
方案2:跨组件透传权限(适合短流程页面流转)
如果你不需要长期保留访问权限,只是要在App内部几个页面跳转后再读取文件,可以不用做持久化,跳转页面时不要只传URI字符串,把权限标记同步透传给目标页面即可:
val jumpIntent = Intent(this, FileHandleActivity::class.java) jumpIntent.data = fileUri // 给目标页面同步授予URI的读权限 jumpIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) startActivity(jumpIntent)
这个方案的权限仍然是临时的,当关联该权限的所有页面都销毁、App进程被杀后,权限会自动失效,无法支持间隔很久的访问场景。
- 不要只存储URI字符串不处理权限:URI本质只是资源路径,没有对应访问权限的话路径正确也无法读取内容
- 部分定制ROM对临时权限的回收逻辑更激进,哪怕还在接收Intent的原页面,切后台一段时间后权限也可能被回收,优先选择持久化权限方案稳定性最高
- 持久化权限仅代表你拥有访问资格,如果原文件被用户、提供方App删除或移动位置,仍然会读取失败,这属于正常的文件访问异常,需要做好异常捕获
内容的提问来源于stack exchange,提问作者Tobias Reich

