我使用ContentResolver.insert
将文件添加到Documents/MyExcelsFolder
,然后还通过另一个应用程序(例如FileManager(将新文件添加到了Documents/MyExcelsFolder
文件夹
然后我尝试从MyExcelsFolder
文件夹中获取所有文件
fun getAppFiles(context: Context): List<AppFile> {
val appFiles = mutableListOf<AppFile>()
val contentResolver = context.contentResolver
val columns = mutableListOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
add(
MediaStore.MediaColumns.RELATIVE_PATH
)
}
}.toTypedArray()
val extensions = listOf("xls", "xlsx")
val mimes = extensions.map { MimeTypeMap.getSingleton().getMimeTypeFromExtension(it) }
val selection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
"${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
} else {
"${MediaStore.Images.Media.DATA} LIKE ?"
}
val selectionArgs = arrayOf(
"%${Environment.DIRECTORY_DOCUMENTS}/MyExcelsFolder%"
)
contentResolver.query(
MediaStore.Files.getContentUri("external"),
columns,
selection,
selectionArgs,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
)?.use { cursor ->
while (cursor.moveToNext()) {
val pathColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
val mimeColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)
val filePath = cursor.getString(pathColumnIndex)
val mimeType = cursor.getString(mimeColumnIndex)
if (mimeType != null && mimes.contains(mimeType)) {
// handle cursor
appFiles.add(cursor.toAppFile())
} else {
// need to check extension, because the Mime Type is null
val extension = File(filePath).extension
if (extensions.contains(extension)) {
// handle cursor
appFiles.add(cursor.toAppFile())
}
}
}
}
return appFiles
}
fun Cursor.toAppFile(): AppFile {
val cursor = this
val idColumnIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
val nameColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val mimeColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)
val pathColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
val id = cursor.getLong(idColumnIndex)
val uri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id)
val fileDisplayName = cursor.getString(nameColumnIndex)
val filePath = cursor.getString(pathColumnIndex)
var mimeType = cursor.getString(mimeColumnIndex)
val relativePath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.RELATIVE_PATH))
} else {
null
}
var type = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
if (type == null) {
type = File(filePath).extension
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(type)
}
return AppFile(
id = id,
uri = uri,
absolutePath = filePath,
name = fileDisplayName,
mimeType = mimeType,
extension = type,
relativePath = relativePath
)
}
结果是insert
命令只添加了来自ContentResolver
的文件,而FileManager没有复制任何文件。如何查看cursor
中的所有文件?
操作系统:Android 10(Q((API 29级(
目标API版本:API 29
从Android 10开始,有一种新的存储访问模型正在运行,它被称为Scoped storage,而且限制性更强。简而言之:
- 您的应用程序始终可以访问自己的目录
- 您的应用程序可以(在
ContentResolver.insert
的帮助下(写入共享媒体收藏集,并且可以从中只读应用程序创建的文件。您可以通过请求READ_EXTERNAL_STORAGE
权限来访问这些集合中的其他应用程序文件 - 您的应用程序可以使用文件或目录系统选择器访问其他文件和目录
您可以通过MediaStore.Files
集合访问xls
文件,这有点奇怪,看起来像是一个bug。文件显示
媒体存储还包括一个名为
MediaStore.Files
的集合。其内容取决于您的应用程序是否使用作用域存储针对Android 10或更高版本的应用程序:如果启用了作用域存储,则集合仅显示照片,视频和音频文件。
如果作用域存储不可用或未被使用,集合显示所有类型的媒体文件。
但无论如何,您仍然无法访问如上所述的其他应用程序创建的文件。因此,根据您的用例,有几个选项可供选择:
- 由于现在可以通过
MediaStore.Files
访问文件,您可以尝试请求READ_EXTERNAL_STORAGE
权限,如本表所示,以获得对媒体收藏集的非过滤访问权限。但我希望这种方式在不同的设备上不可靠地工作,和/或希望在新的更新中停止工作,因为媒体收藏应该只用于媒体文件 - 您可以使用
ACTION_OPEN_DOCUMENT
或ACTION_OPEN_DOCUMENT_TREE
向用户显示文件/目录选择器,并访问文件或整个目录树。请同时检查此方法的限制。我认为这是最好的方式 - 安卓10允许您使用
android:requestLegacyExternalStorage
标志暂时退出作用域存储。但安卓11已经发布了,这个标志在其中没有任何效果 - 您可以请求新的
MANAGE_EXTERNAL_STORAGE
权限,然后由用户请求一个特殊的白名单来访问所有文件。这就是文件管理器现在的工作方式。此功能从Android 11开始提供,因此您可能会在Android 10中使用选择退出标志。如果你打算在那里发布你的应用程序,也一定要检查使用此功能的限制和Google Play政策