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

Posted

技术标签:

【中文标题】为啥 ContentResolver 看不到其他应用添加的文件?【英文标题】:Why ContentResolver does not see added files by another app?为什么 ContentResolver 看不到其他应用添加的文件? 【发布时间】:2020-10-09 08:53:51 【问题描述】:

我使用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 命令添加,文件管理器没有复制文件。如何查看cursor中的所有文件?

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

目标 API 版本:api 29

【问题讨论】:

【参考方案1】:

从 Android 10 开始,有一个新的存储访问模型正在运行,称为 Scoped Storage,它的限制性更强。简而言之:

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

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

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

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

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

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

    由于现在可以通过MediaStore.Files 访问文件,您可以尝试请求READ_EXTERNAL_STORAGE 权限,如this table 所示,以获得对媒体收藏的非过滤访问。但我希望这种方式在不同的设备上运行不可靠和/或希望停止使用新的更新,因为媒体集合应该只用于媒体文件。 您可以使用ACTION_OPEN_DOCUMENTACTION_OPEN_DOCUMENT_TREE 向用户显示文件/目录选择器并访问文件或整个目录树。也请检查此方法的the restrictions。我会说这是最好的方法。 Android 10 允许您使用android:requestLegacyExternalStorage 标志从范围存储中temporarily opt-out。但 Android 11 已经发布,这个标志在其中没有任何作用。 您可以请求新的MANAGE_EXTERNAL_STORAGE 权限,然后向access all files 请求用户的特殊白名单。这就是文件管理器现在的工作方式。此功能从 Android 11 开始可用,因此您可能会为 Android 10 使用选择退出标志。如果您要在那里发布您的应用,请务必检查使用此功能的限制和 Google Play 政策。

【讨论】:

以上是关于为啥 ContentResolver 看不到其他应用添加的文件?的主要内容,如果未能解决你的问题,请参考以下文章

Android开发实践 ContentProvider和ContentResolver

ContentProvider与ContentResolver使用

Android MediaStore:Images.Media.insertImage 与 ContentResolver.insert

内容提供者ContentProvider和内容解析者ContentResolver

内容提供者ContentProvider和内容解析者ContentResolver

内容提供者ContentProvider和内容解析者ContentResolver