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

Why ContentResolver does not see added files by another app?

我使用 ContentResolver.insert 将文件添加到 Documents/MyExcelsFolder,然后还通过另一个应用程序将新文件添加到 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
    )
}

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

操作系统:Android10(Q)(API29级)

目标 API 版本:api 29

从 Android10 开始,有一个新的存储访问模型正在运行,称为 Scoped Storage,它的限制要多得多。简而言之:

  1. 您的应用始终可以访问自己的目录。
  2. 您的应用可以(在 ContentResolver.insert 的帮助下)写入 the shared media collection 并且 只能读取您的应用创建的文件从他们。您可以通过请求 READ_EXTERNAL_STORAGE 权限从这些 collection 访问其他应用程序文件。
  3. 您的应用可以使用 file or directory system pickers.
  4. 访问其他文件和目录

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

The media store also includes a collection called MediaStore.Files. Its contents depend on whether your app uses scoped storage, available on apps that target Android 10 or higher:

If scoped storage is enabled, the collection shows only the photos, videos, and audio files that your app has created.

If scoped storage is unavailable or not being used, the collection shows all types of media files.

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

  1. 由于您现在可以通过 MediaStore.Files 访问文件,您可以尝试请求 READ_EXTERNAL_STORAGE 权限,如 this table 所示,以获得对媒体的 non-filtered 访问权限collection秒。但是我希望这种方式在不同的设备上工作不可靠 and/or 希望停止它与新更新一起工作,因为媒体 collections 应该仅用于媒体文件。
  2. 您可以使用 ACTION_OPEN_DOCUMENTACTION_OPEN_DOCUMENT_TREE 向用户显示 file/directory 选择器并获得对文件或整个目录树的访问权限。请同时检查此方法的 the restrictions。我会说这是更好的方式。
  3. Android 10 允许您使用 android:requestLegacyExternalStorage 标志从范围存储中 temporarily opt-out。但是 Android 11 已经出来了,这个标志对它没有任何影响。
  4. 您可以请求新的 MANAGE_EXTERNAL_STORAGE 权限,然后由用户向 access all files 请求特殊的 white-listing。这就是文件管理器现在的工作方式。此功能从 Android 11 开始可用,因此您可能会为 Android 10 使用 opt-out 标志。另外请务必检查限制和 Google 播放政策如果您打算在那里发布您的应用程序,请使用此功能。