Android 多媒体文件工具类封装(MediaFileUtils)

------ 图片 / 视频扫描、保存相册、分享文件一站式解决方案

在 Android 项目中,经常会遇到以下需求:

  • 获取指定目录下的图片 / 视频文件
  • 从系统相册中读取图片或视频
  • 下载完成后保存到系统相册
  • 分享单个或多个文件(适配 Android 10+)
  • 兼容 Scoped Storage(分区存储)

本文将分享一个完整可用、可直接用于生产环境的 MediaFileUtils 工具类。

一、功能概览

本工具类主要实现以下能力:

✅ 扫描指定目录下的图片 / 视频

✅ 获取系统相册中的图片 / 视频

✅ 支持图片、视频分类

✅ 支持文件分享(单个 / 多个)

✅ 支持 Android 10+ 分区存储

✅ 保存文件到系统相册

✅ 支持自定义文件类型 等

二、核心能力总览

功能 说明
获取目录下文件 支持按图片 / 视频 / 全部筛选
获取系统相册 使用 MediaStore
保存到相册 支持 Android Q+
分享文件 支持 FileProvider
多文件分享 支持 ACTION_SEND_MULTIPLE
兼容性 Android 7.0+
更多 ...

三、核心工具类代码

✅ 可直接复制使用

📌 建议类名:MediaFileUtils.java

java 复制代码
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;

import androidx.activity.result.ActivityResultLauncher;
import androidx.core.content.FileProvider;

import com.xxx.xxx.bean.LocalFileNodeBean;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Author: Su
 * Date: 2025/12/20
 * Description: 工具类:用于获取指定目录下的媒体文件(图片、视频)。
 * 功能:
 * 1. 获取指定目录下所有文件;
 * 2. 可筛选图片、视频或所有文件;
 * 3. 可扩展添加其他类型过滤;
 */

public class MediaFileUtils {

    /**
     * 获取指定路径下所有媒体文件信息(支持过滤类型)
     *
     * @param context   上下文
     * @param basePath  相对路径,如 "MyMedia" 或 "" 获取根路径
     * @param mediaType 需要过滤的媒体类型(IMAGE、VIDEO、ALL)
     * @return 媒体文件列表(按最后修改时间倒序)
     */
    public static List<LocalFileNodeBean> getMediaFileInfos(Context context, String basePath, EnumUtils.MediaFileInfoType mediaType) {
        List<LocalFileNodeBean> mediaFiles = new ArrayList<>();

        if (context == null) return mediaFiles;

        String rootPath = FileUtils.getFilePathDir(context); // 项目中的工具类,返回内部存储根路径
        String fullPath = TextUtils.isEmpty(basePath) ? rootPath : rootPath + File.separator + basePath;

        File directory = new File(fullPath);
        if (!directory.exists() || !directory.isDirectory()) return mediaFiles;

        File[] files = directory.listFiles();
        if (files == null) return mediaFiles;

        for (File file : files) {
            if (!file.isFile()) continue;
            String name = file.getName().toLowerCase();

            if (isImage(name) && (mediaType == EnumUtils.MediaFileInfoType.IMAGE || mediaType == EnumUtils.MediaFileInfoType.ALL)) {
                mediaFiles.add(new LocalFileNodeBean(file, EnumUtils.MediaFileInfoType.IMAGE));
            } else if (isVideo(name) && (mediaType == EnumUtils.MediaFileInfoType.VIDEO || mediaType == EnumUtils.MediaFileInfoType.ALL)) {
                mediaFiles.add(new LocalFileNodeBean(file, EnumUtils.MediaFileInfoType.VIDEO));
            }
        }

        // 按最后修改时间倒序排序
        mediaFiles.sort((a, b) -> Long.compare(b.getLastModified(), a.getLastModified()));
        return mediaFiles;
    }

    /**
     * 获取系统相册中的所有图片文件信息
     *
     * @param context 上下文
     * @return 图片文件列表(按添加时间倒序)
     */
    public static List<LocalFileNodeBean> getAllImageFileInfos(Context context, EnumUtils.MediaFileInfoType mediaType) {
        List<LocalFileNodeBean> imageFiles = new ArrayList<>();
        if (context == null) return imageFiles;

        Uri collection;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
        } else {
            collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        }

        String[] projection = new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATE_ADDED,
                MediaStore.Images.Media.SIZE
        };

        String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";

        try (Cursor cursor = context.getContentResolver().query(
                collection,
                projection,
                null,
                null,
                sortOrder)) {

            if (cursor != null) {
                int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
                int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
                int dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED);
                int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE);

                while (cursor.moveToNext()) {
                    long id = cursor.getLong(idColumn);
                    String name = cursor.getString(nameColumn);
                    long dateAdded = cursor.getLong(dateColumn);
                    long size = cursor.getLong(sizeColumn);

                    // 构建内容 Uri
                    Uri contentUri = ContentUris.withAppendedId(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);

                    LocalFileNodeBean bean = new LocalFileNodeBean(
                            contentUri.toString(),
                            name,
                            size,
                            dateAdded * 1000,
                            mediaType
                    );

                    imageFiles.add(bean);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return imageFiles;
    }

    /**
     * 获取系统相册中的所有图片文件信息
     *
     * @param context 上下文
     * @return 图片文件列表(按添加时间倒序)
     */
    public List<String> getAllImages(Context context) {
        List<String> imagePaths = new ArrayList<>();

        String[] projection = new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATA // 注意:从 Android Q 开始可能废弃
        };

        Uri collection;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
        } else {
            collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        }

        try (Cursor cursor = context.getContentResolver().query(
                collection,
                projection,
                null,
                null,
                MediaStore.Images.Media.DATE_ADDED + " DESC")) {

            if (cursor != null) {
                int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
                int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);

                while (cursor.moveToNext()) {
                    long id = cursor.getLong(idColumn);
                    String name = cursor.getString(nameColumn);

                    Uri contentUri = ContentUris.withAppendedId(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);

                    imagePaths.add(contentUri.toString());
                }
            }
        }

        return imagePaths;
    }


    /**
     * 获取指定目录下的媒体文件
     *
     * @param context    上下文
     * @param basePath   相对路径(如 "MyMedia"),将拼接到内部路径后查找
     * @param mediaType  要获取的媒体类型(图片、视频、全部)
     * @return 媒体文件列表(按最后修改时间倒序)
     */
    public static List<File> getMediaFiles(Context context, String basePath, EnumUtils.MediaFileInfoType mediaType) {
        List<File> result = new ArrayList<>();

        if (context == null || TextUtils.isEmpty(basePath)) return result;

        // 拼接完整目录路径
        String fullPath = FileUtils.getFilePathDir(context) + File.separator + basePath;
        File dir = new File(fullPath);
        if (!dir.exists() || !dir.isDirectory()) return result;

        // 遍历目录
        File[] files = dir.listFiles();
        if (files == null || files.length == 0) return result;

        for (File file : files) {
            if (file.isFile()) {
                String name = file.getName().toLowerCase();
                if (mediaType == EnumUtils.MediaFileInfoType.ALL ||
                        (mediaType == EnumUtils.MediaFileInfoType.IMAGE && isImage(name)) ||
                        (mediaType == EnumUtils.MediaFileInfoType.VIDEO && isVideo(name))) {
                    result.add(file);
                }
            }
        }

        // 按修改时间倒序排序
        result.sort((f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
        return result;
    }

    /**
     * 判断是否为图片文件
     *
     * @param fileName 文件名
     * @return 是否为图片
     */
    private static boolean isImage(String fileName) {
        return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
                fileName.endsWith(".png") || fileName.endsWith(".bmp") ||
                fileName.endsWith(".webp") || fileName.endsWith(".gif");
    }

    /**
     * 判断是否为视频文件
     *
     * @param fileName 文件名
     * @return 是否为视频
     */
    private static boolean isVideo(String fileName) {
        return fileName.endsWith(".mp4") || fileName.endsWith(".avi") ||
                fileName.endsWith(".mov") || fileName.endsWith(".mkv") ||
                fileName.endsWith(".flv") || fileName.endsWith(".3gp");
    }

    /**
     * 分享多个文件(带分享面板结果)
     *
     * @param context   上下文
     * @param fileList  要分享的文件列表
     * @param launcher  ActivityResultLauncher(用于启动系统分享)
     */
    public static void shareMultipleFiles(Context context, List<File> fileList,
                                          ActivityResultLauncher<Intent> launcher) {
        if (fileList == null || fileList.isEmpty()) return;

        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        intent.setType("*/*");

        ArrayList<Uri> uriList = new ArrayList<>();
        for (File file : fileList) {
            if (file.exists()) {
                Uri uri = FileProvider.getUriForFile(
                        context,
                        "com.haizhen.smartcarhome.fileprovider",
                        file
                );
                uriList.add(uri);
            }
        }

        if (uriList.isEmpty()) return;

        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        launcher.launch(Intent.createChooser(intent, "分享文件"));
    }

    /**
     * 分享单个文件(带分享面板结果)
     *
     * @param context   上下文
     * @param file  要分享的文件列表
     * @param launcher  ActivityResultLauncher(用于启动系统分享)
     */
    public static void shareFile(Context context, File file,
                                 ActivityResultLauncher<Intent> launcher) {
        if (file == null) return;

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("video/*");

        if (file.exists()) {
            Uri uri = FileProvider.getUriForFile(
                    context,
                    "com.haizhen.smartcarhome.fileprovider",
                    file
            );
            intent.putExtra(Intent.EXTRA_STREAM, uri);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            launcher.launch(Intent.createChooser(intent, "分享文件"));
        }
    }

    /**
     * 分享单个文件(支持 LocalFileNodeBean 类型)
     *
     * @param context   上下文
     * @param fileNodeBean  要分享的文件对象列表(每项包含文件路径等信息)
     * @param launcher  ActivityResultLauncher(用于启动系统分享)
     */
    public static void shareSpecialFile(Context context, LocalFileNodeBean fileNodeBean,
                                        ActivityResultLauncher<Intent> launcher) {
        if (fileNodeBean == null) return;

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("video/*");

        File file = new File(fileNodeBean.getFilePath());
        if (file.exists()) {
            Uri uri = FileProvider.getUriForFile(
                    context,
                    "com.haizhen.smartcarhome.fileprovider",
                    file
            );
            intent.putExtra(Intent.EXTRA_STREAM, uri);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            launcher.launch(Intent.createChooser(intent, "分享文件"));
        }
    }

    /**
     * 分享多个文件(支持 LocalFileNodeBean 类型)
     *
     * @param context   上下文
     * @param fileList  要分享的文件对象列表(每项包含文件路径等信息)
     * @param launcher  ActivityResultLauncher,用于处理分享回调
     */
    public static void shareSpecialMultipleFiles(Context context,
                                                 List<LocalFileNodeBean> fileList,
                                                 ActivityResultLauncher<Intent> launcher) {
        if (fileList == null || fileList.isEmpty()) return;

        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        intent.setType("*/*");

        ArrayList<Uri> uriList = new ArrayList<>();
        for (LocalFileNodeBean node : fileList) {
            String path = node.getFilePath(); // 你需要确认 getFilePath() 返回的是字符串路径
            if (path == null) continue;

            File file = new File(path);
            if (!file.exists()) continue;

            Uri uri = FileProvider.getUriForFile(
                    context,
                    "com.haizhen.smartcarhome.fileprovider",
                    file
            );
            uriList.add(uri);
        }

        if (uriList.isEmpty()) return;

        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        launcher.launch(Intent.createChooser(intent, "分享文件"));
    }

    /**
     * 保存文件到相册(支持图片或视频)
     *
     * @param context 上下文
     * @param file    要保存的文件
     * @param mimeType 文件类型,如 "image/jpeg"、"video/mp4"
     * @return 是否保存成功
     */
    public static boolean saveFileToAlbum(Context context, File file, String mimeType) {
        if (file == null || !file.exists()) return false;

        try {
            ContentResolver resolver = context.getContentResolver();
            ContentValues values = new ContentValues();

            String relativePath;
            Uri collection;

            // 判断是图片还是视频
            if (mimeType.startsWith("image")) {
                relativePath = Environment.DIRECTORY_PICTURES + "/SmartCarHome";
                collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if (mimeType.startsWith("video")) {
                relativePath = Environment.DIRECTORY_MOVIES + "/SmartCarHome";
                collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else {
                return false;
            }

            values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName());
            values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath);
            values.put(MediaStore.MediaColumns.IS_PENDING, 1);

            // 插入媒体库
            Uri itemUri = resolver.insert(collection, values);
            if (itemUri == null) return false;

            // 写入数据
            try (OutputStream os = resolver.openOutputStream(itemUri)) {
                if (os == null) return false;
                try (InputStream is = new FileInputStream(file)) {
                    byte[] buffer = new byte[4096];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        os.write(buffer, 0, len);
                    }
                }
            }

            // 更新为非 pending 状态
            values.clear();
            values.put(MediaStore.MediaColumns.IS_PENDING, 0);
            resolver.update(itemUri, values, null, null);

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 批量保存文件(LocalFileNodeBean)到系统相册(支持图片/视频,自动推断类型)
     *
     * @param context 上下文
     * @param fileList 文件信息列表
     * @return 成功数量
     */
    public static int saveLocalFileListToAlbum(Context context, List<LocalFileNodeBean> fileList) {
        if (fileList == null || fileList.isEmpty()) return 0;

        int successCount = 0;
        for (LocalFileNodeBean node : fileList) {
            if (node == null) continue;
            File file = new File(node.getFilePath());
            if (!file.exists()) continue;

            String mimeType = getMimeTypeFromFile(file);
            boolean result = saveFileToAlbum(context, file, mimeType);
            if (result) successCount++;
        }
        return successCount;
    }

    /**
     * 通过文件后缀获取 MIME 类型
     */
    public static String getMimeTypeFromFile(File file) {
        String name = file.getName().toLowerCase();
        if (name.endsWith(".jpg") || name.endsWith(".jpeg")) return "image/jpeg";
        if (name.endsWith(".png")) return "image/png";
        if (name.endsWith(".mp4")) return "video/mp4";
        if (name.endsWith(".avi")) return "video/x-msvideo";
        // 可拓展更多格式
        return "*/*";
    }

    /**
     * 分享单个文件
     *
     * @param context   上下文
     * @param bitmapFile  要分享的文件
     * @param launcher  ActivityResultLauncher,用于处理分享回调
     */
    public static void shareBitmapFiles(Context context,
                                        File bitmapFile,
                                        ActivityResultLauncher<Intent> launcher) {
        if (bitmapFile == null) return;

        Uri uri = FileProvider.getUriForFile(
                context,
                "com.xxx.xxx.fileprovider",
                bitmapFile
        );

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("image/*");
        intent.putExtra(Intent.EXTRA_STREAM, uri);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        launcher.launch(Intent.createChooser(intent, "分享文件"));
    }

}

LocalFileNodeBean 类

java 复制代码
public class LocalFileNodeBean implements Comparable<LocalFileNodeBean> {

    private String filePath;
    private String fileName;
    private long fileSize;
    private long lastModified;
    private EnumUtils.MediaFileInfoType fileType;
    public boolean isEditState;   //编辑状态
    public boolean isSelect;   //选中状态
    public String progress;   //下载进度

    public LocalFileNodeBean(File file, EnumUtils.MediaFileInfoType type) {
        this.filePath = file.getAbsolutePath();
        this.fileName = file.getName();
        this.fileSize = file.length();
        this.lastModified = file.lastModified();
        this.fileType = type;
    }

    public LocalFileNodeBean(String filePath, String fileName, long fileSize, long lastModified, EnumUtils.MediaFileInfoType type) {
        this.filePath = filePath;
        this.fileName = fileName;
        this.fileSize = fileSize;
        this.lastModified = lastModified;
        this.fileType = type;
    }


    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public long getFileSize() {
        return fileSize;
    }

    public void setFileSize(long fileSize) {
        this.fileSize = fileSize;
    }

    public EnumUtils.MediaFileInfoType getFileType() {
        return fileType;
    }

    public void setFileType(EnumUtils.MediaFileInfoType fileType) {
        this.fileType = fileType;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    public boolean isEditState() {
        return isEditState;
    }

    public void setEditState(boolean editState) {
        isEditState = editState;
    }

    public boolean isSelect() {
        return isSelect;
    }

    public void setSelect(boolean select) {
        isSelect = select;
    }

    public String getProgress() {
        return progress;
    }

    public void setProgress(String progress) {
        this.progress = progress;
    }

    // --- 支持按时间倒序、名称升序排序 ---
    @Override
    public int compareTo(LocalFileNodeBean other) {
        int result = Long.compare(other.lastModified, this.lastModified); // 倒序:新时间在前

        if (result == 0 && !this.fileName.equals(other.fileName)) {
            return this.fileName.compareToIgnoreCase(other.getFileName()); // 名称升序
        }

        return result;
    }
}
相关推荐
weixin_478433322 小时前
iluwatar 设计模式
java·开发语言·设计模式
csj502 小时前
安卓基础之《(11)—数据存储(1)共享参数SharedPreferences》
android
走在路上的菜鸟2 小时前
Android学Dart学习笔记第二十七节 异步编程
android·笔记·学习·flutter
哆啦安全2 小时前
Android智能调试分析工具V7.5
android
モンキー・D・小菜鸡儿2 小时前
Android 自定义粒子连线动画视图实现:打造炫酷背景效果
android·java
lxysbly2 小时前
安卓 PS1 模拟器,手机上也能玩经典 PlayStation 游戏
android·游戏·智能手机
Java天梯之路2 小时前
# Spring Boot 钩子全集实战(四):`SpringApplicationRunListener.environmentPrepared()` 详解
java·spring·面试
i757_w2 小时前
IDEA快捷键被占用
java·ide·intellij-idea
白鸽(二般)2 小时前
Spring 的配置文件没有小绿叶
java·后端·spring