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

如何在Android中使用Kotlin高效获取设备存储的照片与视频列表?

解决方案:高效获取并合并Android设备的图片与视频列表

一、先处理外部存储权限

根据Android版本差异,所需权限不同:

  • Android 13(API 33)及以上:需READ_MEDIA_IMAGES和READ_MEDIA_VIDEO权限
  • Android 12及以下:需READ_EXTERNAL_STORAGE权限

权限申请实现

用ActivityResultContracts.RequestMultiplePermissions处理运行时权限:

private val requestPermissionsLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    val allGranted = permissions.all { it.value }
    if (allGranted) {
        fetchMediaItems()
    } else {
        Toast.makeText(this, "需要存储权限才能访问媒体文件", Toast.LENGTH_SHORT).show()
    }
}

private fun requestStoragePermissions() {
    val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        arrayOf(
            Manifest.permission.READ_MEDIA_IMAGES,
            Manifest.permission.READ_MEDIA_VIDEO
        )
    } else {
        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
    }
    requestPermissionsLauncher.launch(permissions)
}

二、优化MediaStore查询:一次获取图片+视频

分开查询图片和视频的方式可行,但更高效的是直接查询MediaStore.Files表(包含所有媒体文件),通过过滤类型一次拿到目标数据,减少IO操作。

定义媒体数据类

data class MediaItem(
    val id: Long,
    val displayName: String,
    val dateAdded: Long,
    val uri: Uri,
    val isVideo: Boolean // 区分图片/视频类型
)

高效查询实现

private fun fetchMediaItems(): List<MediaItem> {
    val mediaItems = mutableListOf<MediaItem>()
    
    // 仅查询必需列,避免冗余数据
    val projection = arrayOf(
        MediaStore.Files.FileColumns._ID,
        MediaStore.Files.FileColumns.DISPLAY_NAME,
        MediaStore.Files.FileColumns.DATE_ADDED,
        MediaStore.Files.FileColumns.MEDIA_TYPE
    )
    
    // 过滤图片和视频类型
    val selection = "${MediaStore.Files.FileColumns.MEDIA_TYPE} IN (?, ?)"
    val selectionArgs = arrayOf(
        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
        MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
    )
    
    // 按添加日期倒序排列,最新内容在前
    val sortOrder = "${MediaStore.Files.FileColumns.DATE_ADDED} DESC"

    contentResolver.query(
        MediaStore.Files.getContentUri("external"),
        projection,
        selection,
        selectionArgs,
        sortOrder
    )?.use { cursor ->
        // 预获取列索引,避免硬编码位置
        val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
        val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
        val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_ADDED)
        val mediaTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
        
        while (cursor.moveToNext()) {
            val id = cursor.getLong(idColumn)
            val displayName = cursor.getString(nameColumn)
            val dateAdded = cursor.getLong(dateAddedColumn)
            val mediaType = cursor.getInt(mediaTypeColumn)
            
            // 安全构建媒体URI(适配Android Q+存储限制)
            val contentUri = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            } else {
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            }
            val uri = ContentUris.withAppendedId(contentUri, id)
            
            mediaItems.add(
                MediaItem(
                    id = id,
                    displayName = displayName,
                    dateAdded = dateAdded,
                    uri = uri,
                    isVideo = mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
                )
            )
        }
    }
    
    return mediaItems
}

三、关键优化点说明

  1. 单表查询:用MediaStore.Files替代两次独立查询,降低ContentResolver的IO开销
  2. 最小化投影列:只查询业务需要的字段,避免加载冗余数据
  3. 数据库层排序:直接在查询时指定排序规则,无需内存中二次排序
  4. 安全URI构建:用ContentUris.withAppendedId生成URI,适配Android Q+的存储权限限制(避免使用已废弃的DATA列)

四、使用流程

在Activity中先检查权限,再执行查询:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    if (checkSelfPermission()) {
        fetchMediaItems()
    } else {
        requestStoragePermissions()
    }
}

private fun checkSelfPermission(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED
    } else {
        ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }
}

内容的提问来源于stack exchange,提问作者Lily Monta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:56:01