如何在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 }
三、关键优化点说明
- 单表查询:用
MediaStore.Files替代两次独立查询,降低ContentResolver的IO开销 - 最小化投影列:只查询业务需要的字段,避免加载冗余数据
- 数据库层排序:直接在查询时指定排序规则,无需内存中二次排序
- 安全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
相关产品推荐
相关产品推荐

