Android 11 - 分区存储 - 即使在授予 Uri 权限后也无法使用 java.io.File

Android 11 - Scoped storage - unable to use java.io.File even after Uri permissions are granted

我目前正在将 Android 应用程序迁移到 Scoped Storage. Moving away from java.io.file is a real nightmare, and feels much of a drawback to me. I'm currently trying to understand if paths returned by getExternalMediaDirs() can effectively be used with direct file paths

要求用户select给定文件夹后

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);

并在 onActivityResult

中存储对 read/write 的永久 Uri 权限
getContentResolver().takePersistableUriPermission(data.getData(), (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));

是否预计仍然无法在这样的授权路径中使用java.io.File

SAF 路径:

content://com.android.externalstorage.documents/tree/primary%3AAndroid/media/com.otherapp/test.txt

相当于java.io.File路径

/storage/emulated/0/Android/media/com.otherapp/test.txt

一些最后的说明:

谢谢 尼古拉

是的,因为在授予权限后,您可以访问该特定文件夹的 Content Scheme Uri 而不是文件系统路径,因此您将无法使用此 Uri 列出 java.io.File 个对象。

但是您可以将它用于 listFiles(),这将 return 您得到一个 DocumentFile. You can then use these DocumentFile objects to get Uri of that specific file using documentFile.getUri() 的列表。使用 Uri 您可以轻松地对该文件执行 display,copy,delete,share 等基本操作。

下面是列出其他应用程序文件夹中文件的示例。

假设您已经有权访问其他应用程序的文件夹,我将跳过获取代码的权限部分

在这个例子中,我列出了来自 Android/media/com.whatsapp/WhatsApp/Media/.Statuses

的文件

DOC ID 是

companion object {
    const val ANDROID_DOCID = "primary:Android/media/"
    const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
    private val androidUri = DocumentsContract.buildDocumentUri(
        EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
    )
    val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
        EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
    )
}

获得许可后我们有树Uri

这里的树Uri是content://com.android.externalstorage.documents/tree/primary:Android/media

val dir = DocumentFile.fromTreeUri(
    context,
   treeUri!!
)

这个目录是一个文档文件,现在用它来列出树 Uri 中的所有文件夹。

val selectedPackageName = “com.whatsapp”
val FOLDER_NAME_STATUSES = ".Statuses”;
val selectedRootName = “WhatsApp"
var waStatus: DocumentFile? = null
val statusesFiles = arrayListOf<DocumentFile>()
dir?.listFiles()?.forEach {
    if (it.name.equals(selectedPackageName)) {
        it.listFiles().forEach {
            if (it.name.equals(selectedRootName)) {
                it.listFiles().forEach {
                     if (it.name.equals(Constants.FOLDER_NAME_STATUSES)) {
                                waStatus = it
                         statusesFiles.addAll(waStatus!!.listFiles().filter {
                                    it.mimeType.equals(Constants.MIME_TYPE_IMG_PNG) || it.mimeType.equals(
                                        Constants.MIME_TYPE_IMG_JPG
                                    ) || it.mimeType.equals(Constants.MIME_TYPE_IMG_JPEG)
                                }.sortedByDescending { it.lastModified() })

                            }

                        }


                    }
                }
            }
        }

现在我们有来自另一个应用程序文件夹的 DocumentFile 列表。使用此文件的 Uri 进行进一步的操作,例如 display,copy,delete,share.

另外,阅读评论 你关于 Downloads 文件夹的观点 but would this be the same matter for files in Download

下载是一个 public 目录,我列出了我自己的应用程序使用 listFiles() 创建的文件,它 return 列出了 File.

 val folder = File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
        Constants.DOWNLOADS_APP_DIRECTORY_NAME
    )
      if (folder != null && folder.exists()) {
                val newFiles: Array<File> = statusFolderInDownloads.listFiles({ file ->
                    getFileType(file.path) == FILETYPE.IMAGE || getFileType(file.path) == FILETYPE.VIDEO
                })
                list.addAll(newFiles)
                list.sortByDescending { it.lastModified() }
                if (list.isNotEmpty()) {
                    return list
                } else {
                    return arrayListOf()
                }
            }