如何使用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
相关产品推荐
相关产品推荐

