为什么 ContentResolver 看不到其他应用程序添加的文件?

Nic*_*hek 6 android uri file file-manager android-contentresolver

Documents/MyExcelsFolder 我通过使用添加了文件ContentResolver.insert,然后还Documents/MyExcelsFolder通过另一个应用程序将新文件添加到了文件夹(例如FileManager

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
    )
}
Run Code Online (Sandbox Code Playgroud)

结果只有命令ContentResolver添加的文件insert,没有FileManager复制的文件。如何查看 中的所有文件cursor

操作系统:Android 10 (Q)(API 级别 29)

目标 API 版本:api 29

ese*_*sov 3

从 Android 10 开始,出现了一种新的存储访问模型,称为“作用域存储”,它的限制性更强。简而言之:

  1. 您的应用程序始终可以访问自己的目录。
  2. 您的应用程序可以写入(在 的帮助下ContentResolver.insert)到共享媒体集合,并且只能从中读取您的应用程序创建的文件。您可以通过请求权限来访问这些集合中的其他应用程序文件READ_EXTERNAL_STORAGE
  3. 您的应用程序可以使用文件或目录系统选择器访问其他文件和目录。

这有点奇怪,看起来像是一个错误,您可以xls通过集合访问文件MediaStore.Files文档

媒体商店还包括一个名为 的集合MediaStore.Files。其内容取决于您的应用是否使用范围存储,适用于面向 Android 10 或更高版本的应用:

如果启用了范围存储,则该集合仅显示您的应用创建的照片、视频和音频文件。

如果范围存储不可用或未使用,则该集合会显示所有类型的媒体文件。

但无论如何,您仍然无法访问上述其他应用程序创建的文件。因此,根据您的用例,有以下几种选择:

  1. 当您现在通过工作访问文件时MediaStore.Files,您可以尝试请求此表READ_EXTERNAL_STORAGE中所示的权限,以获得对媒体集合的非过滤访问。但我希望这种方式在不同的设备上工作不可靠,并且/或者希望停止它与新的更新一起工作,因为媒体集合应该仅用于媒体文件。
  2. 您可以使用ACTION_OPEN_DOCUMENTACTION_OPEN_DOCUMENT_TREE向用户显示文件/目录选择器并访问文件或整个目录树。另请检查此方法的限制。我想说这是最好的方法。
  3. Android 10 允许您使用该标志暂时选择退出android:requestLegacyExternalStorage范围存储。但 Android 11 已经发布,该标志对其没有任何影响。
  4. 您可以请求新的MANAGE_EXTERNAL_STORAGE权限,然后请求用户将其列入特殊的白名单以访问所有文件。这就是文件管理器现在的工作方式。此功能从 Android 11 开始可用,因此您可能会使用 Android 10 的选择退出标志。如果您打算在 Google Play 发布应用程序,请务必检查有关使用此功能的限制和 Google Play 政策。