简介:本文深入探讨Android 11中遍历共享目录的实现方法,涵盖存储访问框架(SAF)、权限配置、代码实现及性能优化,帮助开发者高效管理用户共享文件。
Android 11(API 30)引入了分区存储(Scoped Storage)机制,彻底改变了应用对设备存储的访问方式。传统模式下,应用可通过Environment.getExternalStorageDirectory()直接访问所有公共目录,但此方式存在隐私泄露风险。Android 11通过以下核心规则重构存储模型:
Android/data/<package_name>目录,无需权限即可自由读写。MediaStore API访问,需声明READ_EXTERNAL_STORAGE权限。Downloads、DCIM等公共目录,必须通过存储访问框架(Storage Access Framework, SAF)或MANAGE_EXTERNAL_STORAGE权限(需用户手动授权)实现。共享目录定位:在Android 11中,”共享目录”特指用户通过SAF主动选择的目录(如Downloads中的子文件夹),或通过MediaStore暴露的媒体集合。开发者需明确:直接遍历系统级共享目录(如/storage/emulated/0/Download)已被禁止,必须通过合规方式获取访问权限。
SAF是Google推荐的共享文件访问方式,其核心流程如下:
// 使用ACTION_OPEN_DOCUMENT_TREE让用户选择目录Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY);
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == RESULT_OK) {Uri treeUri = data.getData();// 获取持久化权限getContentResolver().takePersistableUriPermission(treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);// 遍历目录内容traverseDirectory(treeUri);}}
优势:符合Android隐私政策,无需声明危险权限。
private void traverseDirectory(Uri treeUri) {DocumentFile root = DocumentFile.fromTreeUri(this, treeUri);if (root != null && root.isDirectory()) {for (DocumentFile file : root.listFiles()) {if (file.isDirectory()) {traverseDirectory(file.getUri()); // 递归子目录} else {Log.d("File", "Name: " + file.getName() + ", Size: " + file.length());}}}}
若目标为照片、视频等媒体文件,可通过MediaStore查询:
// 查询Downloads目录下的图片String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME};String selection = MediaStore.Images.Media.RELATIVE_PATH + " LIKE ?";String[] selectionArgs = {"Download/%"};ContentResolver resolver = getContentResolver();Cursor cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,projection,selection,selectionArgs,null);if (cursor != null) {while (cursor.moveToNext()) {long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME));Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);Log.d("MediaFile", "ID: " + id + ", Name: " + name);}cursor.close();}
适用场景:需处理媒体文件时,可结合MediaStore.Files查询非媒体文件。
若必须访问所有共享目录(如文件管理器类应用),可声明MANAGE_EXTERNAL_STORAGE权限,但需通过Google Play审核:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
if (!Environment.isExternalStorageManager()) {Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);startActivity(intent);}
风险提示:Google Play可能拒绝应用上架,且用户可能拒绝授权。
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);if (downloadsDir.exists()) {File[] files = downloadsDir.listFiles();if (files != null) {for (File file : files) {Log.d("File", "Path: " + file.getAbsolutePath());}}}
遍历大目录时,使用AsyncTask或Coroutine避免阻塞UI线程:
// Kotlin协程示例suspend fun traverseDirectoryAsync(treeUri: Uri) {withContext(Dispatchers.IO) {val root = DocumentFile.fromTreeUri(context, treeUri)root?.listFiles()?.forEach { file ->if (file.isDirectory) {traverseDirectoryAsync(file.uri)} else {// 处理文件}}}}
对频繁访问的目录,缓存文件列表并监听变更:
// 注册内容观察者getContentResolver().registerContentObserver(MediaStore.Downloads.EXTERNAL_CONTENT_URI,true,new ContentObserver(new Handler()) {@Overridepublic void onChange(boolean selfChange) {// 目录内容变更时刷新缓存}});
PersistableUriPermission被撤销事件。RELATIVE_PATH列是否支持(Android 11+)。问题:SAF选择的目录无法写入文件。
解决:确保调用takePersistableUriPermission时包含FLAG_GRANT_WRITE_URI_PERMISSION。
问题:MediaStore查询不到Downloads目录的文件。
解决:检查设备是否启用”存储权限”,或使用DocumentFile通过SAF访问。
问题:MANAGE_EXTERNAL_STORAGE权限被系统自动撤销。
解决:避免频繁请求权限,仅在核心功能需要时提示用户。
Android 11的存储机制变革旨在平衡功能与隐私,开发者需适应以下趋势:
MANAGE_EXTERNAL_STORAGE。MediaStore的功能持续增强,可覆盖大部分文件管理需求。未来Android版本可能进一步收紧存储访问,建议提前规划迁移至合规方案。通过合理组合SAF、MediaStore和权限管理,可在保护用户隐私的同时实现高效的共享目录遍历。