Android 11如何获取目录文件列表?开发轻量文件浏览器遇Scoped Storage难题
在Android 11通过Scoped Storage访问/sdcard目录的方案
嘿,刚好我之前做过类似的自定义轻量文件浏览器,来给你说说Android 11下用Scoped Storage访问/sdcard(外部存储根目录)的正确姿势——毕竟Scoped Storage把直接硬编码访问根目录的路子给堵死了,得换个思路来搞。
核心逻辑:Scoped Storage的权限限制
Android 11及以上版本,Scoped Storage默认只允许你的应用访问自身私有目录和媒体文件(图/音/视频)。如果要访问/sdcard根目录下的非媒体文件(比如文档、压缩包),有两种合法途径:
方法一:使用存储访问框架(SAF)让用户授权(推荐)
这是Google推荐的方式,符合Scoped Storage的设计理念,不需要申请特殊权限,只需让用户手动选择/sdcard目录并授权。
步骤1:启动文档树选择器
用ACTION_OPEN_DOCUMENT_TREEIntent唤起系统的目录选择器,让用户选择/sdcard根目录。注意从API 30开始,startActivityForResult被弃用,推荐用registerForActivityResult:
// 初始化ActivityResultLauncher,处理用户选择后的回调 private val pickRootDirLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { selectedUri -> selectedUri?.let { uri -> // 步骤2:申请持久访问权限,避免重启应用后失去权限 val permissionFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(uri, permissionFlags) // 步骤3:用DocumentFile操作目录内容 val rootDir = DocumentFile.fromTreeUri(this, uri) rootDir?.listFiles()?.forEach { file -> // 遍历根目录下的文件/子目录 val type = if (file.isDirectory) "目录" else "文件" println("${file.name} - $type") } } } // 触发目录选择的方法 fun openSdcardRoot() { // 可选:默认跳转到/sdcard目录(Android 11+支持) val initialSdcardUri = MediaStore.getExternalStorageVolume().storageUri pickRootDirLauncher.launch(initialSdcardUri) }
关键说明:
DocumentFile是SAF的核心操作类,替代了传统的File类,用来遍历、创建、删除文件/目录。- 持久权限
takePersistableUriPermission必须调用,否则应用重启后之前授权的Uri会失效。
方法二:申请MANAGE_EXTERNAL_STORAGE特殊权限(谨慎使用)
如果你的应用是正经的文件管理器、备份工具等符合Google规定的场景,可以申请这个权限来直接访问整个外部存储。但要注意:
- 这个权限是特殊权限,用户需要手动去应用设置里开启。
- Google Play对这个权限的审核非常严格,非合规应用会被拒审。
步骤1:在Manifest中声明权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
步骤2:引导用户开启权限
fun checkAndRequestFullStoragePermission() { if (!Environment.isExternalStorageManager()) { // 跳转到应用的所有文件访问权限设置页 val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:$packageName") startActivity(intent) } else { // 权限已开启,可以用传统File API访问/sdcard了 val sdcardRoot = Environment.getExternalStorageDirectory() sdcardRoot.listFiles()?.forEach { file -> println("${file.name} - ${if (file.isDirectory) "目录" else "文件"}") } } }
最后提醒
- 优先用SAF的方式,因为它更符合Android的安全规范,也不会有审核风险。
- 不要尝试绕过Scoped Storage的限制(比如通过root或者私有API),这会导致应用兼容性问题,甚至被Google Play下架。
内容的提问来源于stack exchange,提问作者Алексей Мальченко
相关产品推荐
相关产品推荐

