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

如何使用Android MediaStore.Files获取外部及SD卡中指定类型文件

使用MediaStore.Files高效获取全存储特定类型文件

一、权限准备

  • Android 6.0-9.0:需动态申请READ_EXTERNAL_STORAGE权限
  • Android 10(API 29):默认分区存储,READ_EXTERNAL_STORAGE可访问媒体文件与下载目录文件;如需访问其他目录,需申请MANAGE_EXTERNAL_STORAGE(非必要不推荐)
  • Android 11+(API 30):访问非媒体/下载目录文件需MANAGE_EXTERNAL_STORAGE权限;PDF多存于下载目录,READ_EXTERNAL_STORAGE可覆盖多数场景;若要遍历SD卡全目录,需申请该权限并引导用户授权

二、核心查询逻辑

MediaStore.Files通过读取系统媒体数据库获取文件,比遍历文件系统快得多,关键是通过过滤条件定位特定类型文件:

1. 只查询必要字段

避免查询全量列,仅获取所需信息减少数据传输:

String[] projection = {
    MediaStore.Files.FileColumns.DISPLAY_NAME,
    MediaStore.Files.FileColumns.RELATIVE_PATH, // Android 10+替代废弃的DATA字段
    MediaStore.Files.FileColumns.SIZE,
    MediaStore.Files.FileColumns.DATE_MODIFIED,
    MediaStore.Files.FileColumns.VOLUME_NAME // 区分主存储/外置SD卡
};

2. 构建过滤条件

针对PDF文件,同时用MIME类型和扩展名双重过滤,避免漏判:

String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? OR " +
                   MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE ?";
String[] selectionArgs = {
    "application/pdf",
    "%.pdf"
};

三、覆盖所有存储卷

MediaStore会自动包含主外部存储(内置SD卡)和可移动外部存储(外置SD卡)的文件,只需指定对应URI查询:

// 查询所有挂载的存储卷
Uri allVolumesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
// 单独查询主存储(部分设备需补充)
Uri primaryVolumeUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);

四、性能优化技巧

  • 异步执行:不要在主线程调用ContentResolver.query,推荐用Executor、协程或LoaderManager实现异步查询
  • 分页加载:文件数量极大时,用LIMIT和OFFSET分页获取,避免内存溢出
  • 监听变化:通过ContentObserver监听媒体库变更,仅在文件变化时重新查询,无需定时轮询

五、完整Java代码示例

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class PdfFileLoader {

    public List<PdfFile> loadAllPdfFiles(ContentResolver contentResolver) {
        List<PdfFile> pdfFiles = new ArrayList<>();
        String[] projection = {
                MediaStore.Files.FileColumns.DISPLAY_NAME,
                MediaStore.Files.FileColumns.RELATIVE_PATH,
                MediaStore.Files.FileColumns.SIZE,
                MediaStore.Files.FileColumns.DATE_MODIFIED,
                MediaStore.Files.FileColumns.VOLUME_NAME
        };
        String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? OR " +
                MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE ?";
        String[] selectionArgs = {"application/pdf", "%.pdf"};

        // 查询所有存储卷
        queryFiles(contentResolver, pdfFiles, MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), projection, selection, selectionArgs);
        // 补充查询主存储
        queryFiles(contentResolver, pdfFiles, MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), projection, selection, selectionArgs);

        return pdfFiles;
    }

    private void queryFiles(ContentResolver resolver, List<PdfFile> resultList, Uri uri, String[] projection, String selection, String[] args) {
        Cursor cursor = resolver.query(uri, projection, selection, args, null);
        if (cursor == null) return;

        int nameIdx = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME);
        int pathIdx = cursor.getColumnIndex(MediaStore.Files.FileColumns.RELATIVE_PATH);
        int sizeIdx = cursor.getColumnIndex(MediaStore.Files.FileColumns.SIZE);
        int dateIdx = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATE_MODIFIED);
        int volumeIdx = cursor.getColumnIndex(MediaStore.Files.FileColumns.VOLUME_NAME);

        while (cursor.moveToNext()) {
            String name = cursor.getString(nameIdx);
            String relativePath = cursor.getString(pathIdx);
            long size = cursor.getLong(sizeIdx);
            long dateModified = cursor.getLong(dateIdx) * 1000;
            String volumeName = cursor.getString(volumeIdx);

            // 构建完整文件路径
            File storageDir = MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(volumeName)
                    ? Environment.getExternalStorageDirectory()
                    : getRemovableStorageDir(volumeName);
            String fullPath = storageDir != null ? storageDir.getAbsolutePath() + "/" + relativePath + name : null;

            if (fullPath != null) {
                resultList.add(new PdfFile(name, fullPath, size, dateModified));
            }
        }
        cursor.close();
    }

    // 获取外置SD卡根目录(需适配不同设备)
    private File getRemovableStorageDir(String volumeName) {
        // 可通过StorageManager获取对应存储卷的路径,此处简化示例
        File[] externalDirs = Environment.getExternalStorageDirectories();
        for (File dir : externalDirs) {
            if (!dir.equals(Environment.getExternalStorageDirectory())) {
                return dir;
            }
        }
        return null;
    }

    public static class PdfFile {
        public String name;
        public String path;
        public long size;
        public long dateModified;

        public PdfFile(String name, String path, long size, long dateModified) {
            this.name = name;
            this.path = path;
            this.size = size;
            this.dateModified = dateModified;
        }
    }
}

六、注意事项

  • Android 10+已废弃DATA字段,必须用RELATIVE_PATH结合VOLUME_NAME构建文件路径
  • 若需访问SD卡中未被媒体库索引的自定义目录,Android 11+需申请MANAGE_EXTERNAL_STORAGE权限
  • 测试时需覆盖不同Android版本,重点验证分区存储下的文件访问逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:18:09