Android API 29及以上图片/视频添加到图库实现与旧兼容问题处理
问题原因
- Android 10(API 29)开始
MediaStore.MediaColumns.DATA字段正式废弃,系统禁止应用直接通过文件路径操作公共目录媒体文件,原有直接写入路径的逻辑在API 29及以上版本会失效。 - 视频时长显示为00:00的核心原因是插入媒体库时未填写
MediaStore.Video.Media.DURATION字段,系统无法读取到视频时长信息,默认显示为0。
适配方案
前置权限配置
在AndroidManifest.xml中添加对应权限:
<!-- Android 13及以上 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- Android 10-12,仅保存自身应用生成的媒体文件时可省略 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
修复后完整代码
import android.content.ContentValues import android.content.Context import android.media.MediaMetadataRetriever import android.os.Build import android.provider.MediaStore import android.webkit.MimeTypeMap import java.io.File import java.io.FileInputStream import java.io.OutputStream fun Context.addToGallery(file: File) { if (!file.exists()) return // 区分媒体类型 val isVideo = file.extension.equals("mp4", ignoreCase = true) val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension) ?: if (isVideo) "video/mp4" else "image/jpeg" // 构建ContentValues val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, file.name) put(MediaStore.MediaColumns.MIME_TYPE, mimeType) put(MediaStore.MediaColumns.SIZE, file.length()) // API 29及以上指定存储路径,默认存到公共DCIM目录 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.MediaColumns.RELATIVE_PATH, if (isVideo) "DCIM/Video" else "DCIM/Image") put(MediaStore.MediaColumns.IS_PENDING, 1) } // 视频补充时长字段 if (isVideo) { val duration = getVideoDuration(file) if (duration > 0) { put(MediaStore.Video.Media.DURATION, duration) } } } val contentUri = if (isVideo) { MediaStore.Video.Media.EXTERNAL_CONTENT_URI } else { MediaStore.Images.Media.EXTERNAL_CONTENT_URI } contentResolver.insert(contentUri, values)?.let { uri -> var outputStream: OutputStream? = null var inputStream: FileInputStream? = null try { outputStream = contentResolver.openOutputStream(uri) inputStream = FileInputStream(file) inputStream.copyTo(outputStream!!) // 写入完成后更新状态,通知系统扫描 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.clear() values.put(MediaStore.MediaColumns.IS_PENDING, 0) contentResolver.update(uri, values, null, null) } } catch (e: Exception) { // 写入失败删除无效uri contentResolver.delete(uri, null, null) e.printStackTrace() } finally { outputStream?.close() inputStream?.close() } } } // 读取本地视频文件时长 private fun getVideoDuration(videoFile: File): Long { val retriever = MediaMetadataRetriever() return try { retriever.setDataSource(videoFile.absolutePath) val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) durationStr?.toLongOrNull() ?: 0L } catch (e: Exception) { e.printStackTrace() 0L } finally { retriever.release() } }
关键逻辑说明
- 放弃直接写入
DATA字段的逻辑,改为通过contentResolver.insert拿到媒体uri后,通过流拷贝的方式写入文件内容,兼容所有Android版本。 - 新增视频时长读取逻辑,将时长写入
MediaStore.Video.Media.DURATION字段,修复图库显示视频时长为0的问题。 - API 29及以上通过
RELATIVE_PATH指定媒体文件存储到公共DCIM目录,设置IS_PENDING标记避免系统在文件写入完成前扫描媒体。 - 增加异常处理逻辑,写入失败时自动删除无效的媒体库记录,避免图库出现空白无效文件。
内容的提问来源于stack exchange,提问作者mahmoud mahmoudi
相关产品推荐
相关产品推荐

