背景
Android 10和11存在各种存储限制, 这也包括一个新的权限(MANAGE_EXTERNAL_STORAGE)来访问所有文件(但它并不允许访问真正的所有文件),而之前的存储权限被减少以仅授予访问媒体文件的权限:
- 应用程序可以自由地访问“media”子文件夹。
- 应用程序永远无法访问“data”子文件夹,特别是内容。
- 对于“obb”文件夹,如果允许应用程序安装应用,则可以访问它(将文件复制到其中)。否则就不能。
- 使用USB或root,您仍然可以访问它们,并且作为最终用户,您可以通过内置文件管理器应用程序“文件”访问它们。
问题
我注意到有一个应用程序一些方式克服了这个限制(此处)称为 "X-plore":一旦你进入 “Android/data” 文件夹,它会要求您直接使用 SAF 授予权限,当您授予权限后,您可以访问 "Android" 文件夹中所有文件夹中的所有内容。
这意味着可能仍然有一种方法可以访问它,但问题在于出于某种原因我无法制作一个做到同样的事情的样本。
我发现和尝试过的东西
它似乎针对 API 29(Android 10),并且尚未使用新的权限,而且它具有标志 requestLegacyExternalStorage。我不知道他们使用的同样的技巧是否适用于目标 API 30,但我可以说,在我的情况下,在 Pixel 4 上运行 Android 11,它运行良好。
所以我尝试做同样的事情:
我制作了一个示例 POC,目标是 Android API 29,并授予了所有类型的存储权限,包括遗留标志。
我尝试直接请求访问“Android”文件夹(基于此处),但不幸的是,由于某种原因它没有工作(一直进入 DCIM 文件夹,不知道为什么):
val androidFolderDocumentFile = DocumentFile.fromFile(File(primaryVolume.directory!!, "Android"))
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidFolderDocumentFile.uri)
startActivityForResult(intent, 1)
我尝试了各种标志组合。
启动应用程序时,手动到达“Android”文件夹,因为这个方法没有很好地工作,我像在其他应用程序上一样授予了对该文件夹的访问权限。
获取结果时,尝试获取路径中的文件和文件夹,但无法获取:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AppLog", "resultCode:$resultCode")
val uri = data?.data ?: return
if (!DocumentFile.isDocumentUri(this, uri))
return
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val fullPathFromTreeUri = FileUtilEx.getFullPathFromTreeUri(this, uri) // code for this here: https://dev59.com/uek5XIcBkEYKwwoY49sn
val documentFile = DocumentFile.fromTreeUri(this, uri)
val listFiles: Array<DocumentFile> = documentFile!!.listFiles() // this returns just an array of a single folder ("media")
val androidFolder = File(fullPathFromTreeUri)
androidFolder.listFiles()?.forEach {
Log.d("AppLog", "${it.absoluteFile} children:${it.listFiles()?.joinToString()}") //this does find the folders, but can't reach their contents
}
Log.d("AppLog", "granted uri:$uri $fullPathFromTreeUri")
}
所以使用DocumentFile.fromTreeUri,我仍然只能得到无用的“media”文件夹,而使用File类,我只能看到还有“data”和“obb”文件夹,但仍然无法访问它们的内容...所以这根本不起作用。
后来我发现另一个使用此技巧的应用程序,名为“MiXplorer”。在此应用程序上,它无法直接请求“Android”文件夹(也许它甚至没有尝试),但是一旦您允许它,它确实授予您对其及其子文件夹的完全访问权限。而且,它针对API 30进行了定位,这意味着它之所以不起作用,仅仅是因为您的目标是API 29。
我注意到(有人给我写信)通过对代码进行一些更改,我可以单独请求每个子文件夹的访问权限(即请求“data”和请求“obb”),但这不是我在这里看到的应用程序所做的。
也就是说,要进入“Android”文件夹,我需要将此Uri用作Intent.EXTRA_INITIAL_URI的参数:
val androidUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android").build()
然而,一旦您获得了访问权限,您将无法通过File或SAF获取其文件列表。但是,正如我所写的那样,奇怪的是,如果您尝试类似的操作,即访问"Android/data",您将能够获取其内容。
val androidDataUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android/data").build()
问题
- 我如何直接向“Android”文件夹请求一个意图,以便实际让我访问它,并让我获取子文件夹及其内容?
- 是否有其他替代方法?也许使用adb和/或root,我可以授予SAF对此特定文件夹的访问权限?
// Provide read access to files and sub-directories in the user-selected // directory. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
。嗯,这个文档中有一个错误。将其删除,你就会发现。再想一想:如果没有WRITE标志,它怎么能运行呢? - blackapps