You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 12+下能否通过MediaStore访问USB OTG设备中的图片?

问题解答

核心结论

不能直接通过MediaStore访问USB OTG设备(SD卡读卡器)中的图片。

MediaStore.Images.Media.EXTERNAL_CONTENT_URI仅负责索引设备内部存储、内置SD卡的媒体文件,USB OTG这类通过Storage Access Framework(SAF)挂载的可移除外部存储,不在MediaStore的自动索引范围内,Android 12+也未改变这一机制。

SAF性能优化方案

既然必须使用SAF,针对你当前遍历文件对比大小和修改时间的场景,可以通过以下方式大幅提升性能:

优化思路

避免使用DocumentFile.listFiles()逐个遍历文件(此方法会发起大量独立的ContentResolver请求,性能极低),改用批量查询的方式直接获取目标目录下所有文件的大小和修改时间,再做匹配。

优化后的代码示例

// 替换原onActivityResult中的逻辑
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
    scope.launch {
        val treeUri = data?.data ?: return@launch
        
        // 构建用于查询子文件的URI
        val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
            treeUri,
            DocumentsContract.getTreeDocumentId(treeUri)
        )
        
        // 定义要查询的字段:文件ID、大小、修改时间、文件名
        val projection = arrayOf(
            DocumentsContract.Document.COLUMN_DOCUMENT_ID,
            DocumentsContract.Document.COLUMN_SIZE,
            DocumentsContract.Document.COLUMN_LAST_MODIFIED,
            DocumentsContract.Document.COLUMN_DISPLAY_NAME
        )
        
        activity.contentResolver.query(childrenUri, projection, null, null, null)?.use { cursor ->
            // 遍历游标,批量获取文件信息
            while (cursor.moveToNext()) {
                val fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE))
                val lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED))
                
                filesToMatch?.forEach { fileToMatch ->
                    if (fileSize == fileToMatch.length() && lastModified == fileToMatch.lastModified()) {
                        // 获取匹配文件的URI
                        val docId = cursor.getString(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID))
                        val fileUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, docId)
                        // 执行你的操作
                    }
                }
            }
        }
    }
}

额外优化建议

  1. 如果需要递归查询子目录,可以通过递归调用上述查询逻辑,针对每个子目录的文档ID构建新的查询URI。
  2. Android 12+推荐使用registerForActivityResult替代startActivityForResult,避免过时API:
private val openDocumentTreeLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { treeUri ->
    treeUri?.let {
        // 执行上述批量查询逻辑
    }
}

// 启动选择器的方法
fun startIntent() {
    val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
    storageManager.storageVolumes.forEach { volume ->
        if (volume.isRemovable) {
            val intent = volume.createOpenDocumentTreeIntent()
            openDocumentTreeLauncher.launch(intent)
        }
    }
}

内容的提问来源于stack exchange,提问作者Brien Crean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:16:18