最近在写我的个人应用KeepassA时,遇到了android文件uri生命周期的问题,被文件真实路径的获取搞得有点懵逼。从android p开始,谷歌对文件访问的权限限制的更加变态了,如果想兼容大多数机型,使用谷歌提供的ASF框架是一个很好的选择。

但是该框架只能返回文件的uri,并不能返回文件路径,并且随着android的进一步升级,特别是从android Q 开始,已经完全无法从uri获取到文件真实路径了!!

一、uri 过期

出现的异常有:

java.lang.SecurityException: Permission Denial: opening provider com.android.externalstorage.ExternalStorageProvider

原因:

1、如果你通过AFS获取文件uri使用Intent.ACTION_OPEN_DOCUMENTIntent.ACTION_GET_CONTENT 时,系统只会给你一个临时的uri访问权限,当你的设备重新启动后,该uri就没有了权限。
如:

Intent.ACTION_GET_CONTENT
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
this.type = type
addCategory(Intent.CATEGORY_OPENABLE)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

解决方案:

https://developer.android.google.cn/training/data-storage/shared/documents-files#persist-permissions
向android系统请求长期权限

val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context!!.contentResolver.takePersistableUriPermission(data.data, takeFlags)

注意:该请求需要在onActivityResult 申请

2、自己拼接的uri 没有设置

如果不是访问文件的uri,那么你需要手动设置authority

二、No persistable permission grants

原因:一般是手动Uri.Builder导致的问题,系统不认该uri。
如:

val temp = Uri.parse(uriString)
val ub = Uri.Builder()
ub.authority("com.android.externalstorage.documents") // 必须设置,否则8.0上会出现崩溃的问题
val uri = ub.scheme(temp.scheme)
.path(temp.path)
.appendPath(temp.path)
.encodedPath(temp.encodedPath)
.fragment(temp.fragment)
.encodedFragment(temp.encodedFragment)
.query(temp.query)
.encodedQuery(temp.encodedQuery)
.build()

三、获取已授权的Uri

即使我们申请了长期的uri权限,但是如果该uri对应的文件或uri被移动或删除了,应用依然会删除该uri的权限,需要重新申请uri权限。因此,在每次使用uri打开文件时,最好检查应用是否还保留该uri的权限。

获取已授权的的uri列表:

val list = context!!.contentResolver.persistedUriPermissions