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
一些最后的说明:
- 这样的路径可以用SAF写
- 这样的路径不能直接写成
java.io.File
路径(EACCES
例外)
/Android/media/com.otherapp
不同于自归因 /Android/media/com.app
android.permission.MANAGE_EXTERNAL_STORAGE
不可用
谢谢
尼古拉
是的,因为在授予权限后,您可以访问该特定文件夹的 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()
}
}
我目前正在将 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
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
一些最后的说明:
- 这样的路径可以用SAF写
- 这样的路径不能直接写成
java.io.File
路径(EACCES
例外) /Android/media/com.otherapp
不同于自归因/Android/media/com.app
android.permission.MANAGE_EXTERNAL_STORAGE
不可用
谢谢 尼古拉
是的,因为在授予权限后,您可以访问该特定文件夹的 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()
}
}