Android 11三星平板获取外置SD卡路径读取文件失败如何解决
问题排查与修复方案
你遇到的访问失败是API认知错误、权限配置错误、实现方式过时共同导致的,逐个对应问题如下:
- 你用的基础存储API本身就不返回SD卡路径:从Android 4.4版本开始,
Environment.getExternalStorageDirectory()、Environment.getExternalStorageState()、Environment.getExternalStoragePublicDirectory()这一系列API的返回值固定指向设备内置共享存储,也就是你拿到的/storage/emulated/0路径,设计上就不会返回可移除SD卡的路径,和设备厂商、系统版本无关。 - 你尝试的几种取SD卡路径的方式全部失效:
System.getenv("SECONDARY_STORAGE")是Android 4.3及更早版本使用的临时环境变量,Android 5.0之后系统已经不再写入这个值,返回的路径全是历史遗留的硬编码无效路径,和当前设备实际挂载点完全不匹配。- 你调用
Environment.getExternalStoragePublicDirectory(Environment.MEDIA_MOUNTED)属于参数传错:MEDIA_MOUNTED是用来标识存储挂载状态的字符串常量,不是目录类型常量,传入这个参数等价于在内置存储下查找名为mounted的文件夹,返回的路径自然没有意义。 - 硬编码
/sdcard/external_sd这类路径没有任何兼容性:不同厂商、不同系统版本的SD卡挂载点命名规则完全不同,Android 5.0之后可移除SD卡普遍挂载在/storage/下以卷序列号命名的随机目录(类似/storage/1234-ABCD格式),硬编码固定路径100%会出现匹配失败。
- 你的权限配置存在明显错误:
- 你清单里声明的
android.permission.PERMISSIONS_STORAGE、android.permission.REQUEST_EXTERNAL_STORAGE不是Android系统定义的合法权限,系统解析清单时会直接忽略这两个条目,没有任何实际作用。 - Android 11系统上,
READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE两个权限仅能访问内置共享存储内的媒体文件,无权访问可移除SD卡的文件系统,哪怕你写对了SD卡的真实路径,用File类直接访问也会被系统沙箱拦截,抛出文件不存在的异常,和文件实际是否存在无关。
- 你清单里声明的
正确实现方式
你只需要读取SD卡已存在的文件,不需要申请高风险的全文件访问权限,用Android官方提供的存储访问框架(SAF)实现即可,全机型兼容:
- 先清理清单文件,删除那两个无效的权限声明,不需要额外加特殊权限。
- 不要尝试通过环境变量、硬编码方式获取SD卡真实路径,也不要用
File类直接访问SD卡路径,通过系统服务获取已挂载的可移除存储卷:val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager // 过滤出已正常挂载的可移除SD卡 val mountedSdCard = storageManager.storageVolumes.firstOrNull { it.isRemovable && it.state == Environment.MEDIA_MOUNTED } ?: return // 没有检测到已挂载的SD卡 - 调用存储卷提供的方法拉起系统授权器,让用户选择你要访问的SD卡根目录,拿到系统授权的Uri后即可正常读取、遍历文件:
// 拉起SD卡授权界面 val authIntent = mountedSdCard.createOpenDocumentTreeIntent() startActivityForResult(authIntent, REQUEST_CODE_SD_CARD_AUTH) - 在授权结果回调中拿到访问权限,后续通过
DocumentFile类操作文件即可,不需要关心真实物理路径:override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SD_CARD_AUTH && resultCode == Activity.RESULT_OK) { val sdCardRootUri = data?.data ?: return // 持久化存储授权,下次应用启动不需要重复申请 contentResolver.takePersistableUriPermission( sdCardRootUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) // 遍历/读取SD卡文件示例 val sdCardRoot = DocumentFile.fromTreeUri(this, sdCardRootUri) sdCardRoot?.listFiles()?.forEach { doc -> Log.d("SD_CARD_FILE", "文件名:${doc.name}, 文件大小:${doc.length()}") } } }
补充说明:Android 10及以上版本对可移除存储做了强制沙箱隔离,第三方应用没有权限直接通过原生文件路径访问SD卡内容,必须通过系统返回的Uri、借助
ContentResolver和DocumentFile操作,这也是其他可以正常访问SD卡的应用通用的实现方式。
内容的提问来源于stack exchange,提问作者peterc
相关产品推荐
相关产品推荐

