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

Android中使用MediaStore.Video保存视频至图库的方案咨询及代码实现

Android视频压缩后存入图库方案分析与实现

现有方案合理性分析

你当前的方案在Android 10(API29)及以下版本可以正常工作,但在Android 11(API30)及以上版本会出现兼容性问题:

  • API30+强制启用Scoped Storage,WRITE_EXTERNAL_STORAGE权限不再生效,requestLegacyExternalStorage=true会被系统忽略,应用无法直接通过文件路径写入公共Movies目录
  • 直接操作Environment.getExternalStoragePublicDirectory返回的路径,在API30+会触发权限异常,导致视频无法保存

现有方案代码&权限配置

核心代码

public String getVideoFilePath() {
    return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + "/" + System.currentTimeMillis() + "compress.mp4";
}

public static void exportMp4ToGallery(Context context, String filePath) {
    File file = new File(filePath);
    MediaScannerConnection.scanFile(context, new String[]{file.toString()}, null, null);
}

权限配置

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
android:requestLegacyExternalStorage="true"

是否应该改用MediaStore.Video?

必须改用MediaStore.Video,原因如下:

  • 适配Android 11+的Scoped Storage规范,API30+无需申请WRITE_EXTERNAL_STORAGE权限
  • 官方推荐的共享媒体文件访问方式,兼容性覆盖所有Android版本
  • 系统自动管理媒体文件索引,无需手动调用MediaScannerConnection.scanFile

MediaStore.Video实现直接写入公共图库的代码示例

以下代码直接将压缩后的视频写入系统公共Movies目录,无需先存应用私有目录:

/**
 * 将压缩后的视频写入系统公共Movies目录,返回对应Uri
 * @param context 上下文
 * @param compressedVideoPath 压缩后视频的本地路径(建议存应用私有目录)
 * @return 写入MediaStore后的Uri,失败返回null
 */
public Uri saveCompressedVideoToGallery(Context context, String compressedVideoPath) {
    ContentResolver resolver = context.getContentResolver();
    String videoFileName = "compress_" + System.currentTimeMillis() + ".mp4";
    ContentValues values = new ContentValues();

    // 设置视频元数据
    values.put(MediaStore.Video.Media.TITLE, videoFileName);
    values.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
    values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
    values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
    values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());

    Uri collectionUri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 用RELATIVE_PATH指定存储位置
        values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
        collectionUri = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    } else {
        // Android 9及以下,直接指定文件路径
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath();
        File videoFile = new File(directory, videoFileName);
        values.put(MediaStore.Video.Media.DATA, videoFile.getAbsolutePath());
        collectionUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }

    // 插入元数据,获取写入Uri
    Uri savedVideoUri = resolver.insert(collectionUri, values);
    if (savedVideoUri == null) {
        return null;
    }

    // 写入视频文件内容
    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(savedVideoUri, "w");
         FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor());
         FileInputStream inputStream = new FileInputStream(compressedVideoPath)) {

        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        // Android 10+ 标记文件为已完成
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.clear();
            values.put(MediaStore.Video.Media.IS_PENDING, 0);
            resolver.update(savedVideoUri, values, null, null);
        }

    } catch (IOException e) {
        // 写入失败,删除已插入的元数据
        resolver.delete(savedVideoUri, null, null);
        e.printStackTrace();
        return null;
    }

    return savedVideoUri;
}

使用说明

  1. 压缩类可先将视频保存到应用私有目录(比如getExternalFilesDir("compressed")),此路径无需额外权限
  2. 调用上述方法传入压缩后的视频路径,即可完成写入
  3. API30+无需申请存储权限,API29及以下需动态申请WRITE_EXTERNAL_STORAGE

关于“先存应用外部目录再复制到图库”方案的说明

你提到的该方案完全可行,适合以下场景:

  • 需要保留视频的应用私有副本,删除应用时自动清理这些文件
  • 压缩过程需要临时存储,避免占用公共目录空间

该方案核心逻辑没问题,可优化点:用ContentResolver.openOutputStream替代手动文件流复制,代码更简洁;API30+无需任何存储权限即可写入应用私有外部目录。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 12:50:25