Android 11(API 级别 30)开始,对于访问其他应用的 Android/data 目录,系统对应用程序的访问权限进行了限制。在本文中,我们将探讨如何在 Android 11 及更高版本中适配这种变化,以请求访问其他应用的 Android/data 目录。
内容 URI 规则
在开始实现解决方案之前,让我们先了解一下 Android 中的内容 URI 的规则和约定。URI(统一资源标识符)是一种用于标识互联网上的资源的字符串。在 Android 中,内容 URI 是由内容提供程序(Content Provider)管理的资源的特殊类型的 URI。
以下是 Android 中的内容 URI 的一般结构:
content://authority/path
content://:这是内容 URI 的方案,表示资源由内容提供程序(Content Provider)管理。authority:这部分是内容提供程序的唯一标识符。通常是应用程序的包名,加上一个特定的字符串(例如,com.android.externalstorage.documents)。path:这部分是由内容提供程序定义的资源路径。路径通常包括资源类型(如document或image等)和资源 ID。资源 ID 可以是数据库记录的 ID、文件名等。
了解了内容 URI 的规则后,我们可以继续实现解决方案。
使用 Storage Access Framework 请求访问权限
SAF 提供了一种标准化的方式来让应用程序请求访问其他应用的文件和目录。要使用 SAF 请求访问 Android/data 目录,请按照以下步骤操作:
1. 创建一个用于打开文档树的 Intent
首先,您需要创建一个包含 Intent.ACTION_OPEN_DOCUMENT_TREE 动作的 Intent。这个动作会启动一个文件选择器,让用户选择一个目录(文档树)。
为了让文件选择器默认显示 Android/data 目录,您可以将以下 URI 作为初始 URI:
content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata
创建 Intent 的代码如下:
val uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata")
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
}
2. 使用 ActivityResultLauncher 启动 Intent
现在,您需要使用 ActivityResultLauncher<Intent> 对象来启动创建的 Intent。这是 AndroidX Activity 库中的一个新 API,用于替换过时的 startActivityForResult() 方法。
@RequiresApi(Build.VERSION_CODES.O)
fun requestAndroidDataPermission(launcher: ActivityResultLauncher<Intent>) {
// ...
launcher.launch(intent)
}
3. 处理返回的结果
当用户选择了一个目录并授权访问权限后,您需要处理返回的结果。这可以通过在 ActivityResultLauncher<Intent> 对象的回调中执行以下操作来实现:
fun handleAndroidDataPermissionResult(activity: ComponentActivity, activityResult: ActivityResult) {
activityResult.data?.let { intent ->
intent.data?.let { uri ->
activity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
}
}
在此回调中,您需要调用 takePersistableUriPermission() 方法,以获取持久性的 URI 权限。这样,您就可以在应用程序的整个生命周期内访问所选目录。
4. 检查是否已获得访问权限
在某些情况下,您可能需要检查应用程序是否已获得访问其他应用的 Android/data 目录的权限。为此,您可以编写一个方法,如下所示:
fun isGrantAndroidDataDirPermission(): Boolean {
val uriPermissionList = Utils.getApp().contentResolver.persistedUriPermissions
for (persistedUriPermission in uriPermissionList) {
if (persistedUriPermission.isReadPermission && persistedUriPermission.uri.toString() == "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata") {
return true
}
}
return false
}
这个方法会检查 persistedUriPermissions 列表中是否存在所需的 URI 权限。如果找到匹配的权限,该方法将返回 true,表示应用程序已获得访问其他应用的 Android/data 目录的权限。
总结
在本文中,我们讨论了如何在 Android 11 及更高版本中适配对其他应用的 Android/data 目录的访问权限。通过使用 Storage Access Framework(SAF),您可以让用户主动授权访问这些目录。尽管这种方法比之前的版本更繁琐,但它确保了应用程序之间的数据隔离,从而提高了安全性和隐私保护。
要适配这些更改,请遵循上述步骤创建和启动用于打开文档树的 Intent,处理返回的结果以获取持久性的 URI 权限,并检查是否已获得访问权限。这将使您的应用程序能够在 Android 11 及更高版本中正常访问其他应用的 Android/data 目录