Android 13及以后读写外置SD卡任意文件的方法与权限咨询
Android 13及更高版本外置SD卡读写操作指南
一、读取任意位置的任意文件
Android 13+的存储权限模型大幅收紧,*READ_MEDIA_系列权限仅针对图片、视频、音频这类媒体文件,无法读取txt、html等非媒体文件,也无法访问SD卡上的任意目录。要实现任意位置文件读取,有两种可行方案:
方案1:存储访问框架(SAF)——推荐(符合Google规范)
这是官方推荐的方式,通过让用户手动选择要访问的文件或目录来获取权限:
- 无需申请传统存储权限,只需通过Intent唤起系统文件选择器:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(intent, REQUEST_CODE_PICK_DIR) - 在
onActivityResult(或Activity Result API)中获取用户选择的目录Uri,后续用DocumentFile类遍历目录、读取文件:override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_PICK_DIR && resultCode == RESULT_OK) { data?.data?.let { treeUri -> // 持久化权限,避免重启后丢失 contentResolver.takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) // 遍历目录下的文件 val rootDir = DocumentFile.fromTreeUri(this, treeUri) rootDir?.listFiles()?.forEach { file -> // 获取文件名、类型等信息 val fileName = file.name val mimeType = file.type } // 读取文件内容 val inputStream = contentResolver.openInputStream(file.uri) val content = inputStream?.bufferedReader().use { it?.readText() } } } } - 针对Downloads文件夹的非媒体文件:也可以用
ACTION_OPEN_DOCUMENT让用户直接选择该文件,或者用SAF选择Downloads目录,流程和上面一致。
方案2:MANAGE_EXTERNAL_STORAGE特殊权限——仅适用于文件管理器类应用
这个权限可以获取全存储访问权,但Google Play审核严格,仅允许文件管理器、备份类等核心功能需要的应用申请:
- 在Manifest中声明权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" /> - 动态申请权限:
if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:$packageName") startActivity(intent) } - 获得权限后,就可以用传统的
File类操作SD卡任意位置的文件,但注意这种方式不符合现代存储规范,非必要不推荐。
二、向外置SD卡任意位置写入文件
和读取逻辑一致,核心也是两种方案:
方案1:存储访问框架(SAF)——推荐
- 让用户选择要写入的目录(或直接创建文件):
// 创建新文件 val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/plain" // 根据文件类型设置MIME putExtra(Intent.EXTRA_TITLE, "new_file.txt") } startActivityForResult(intent, REQUEST_CODE_CREATE_FILE) - 获取文件Uri后写入内容:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_CREATE_FILE && resultCode == RESULT_OK) { data?.data?.let { fileUri -> contentResolver.openOutputStream(fileUri)?.use { outputStream -> outputStream.write("写入的内容".toByteArray()) } } } } - 如果是向已存在的文件写入,用
ACTION_OPEN_DOCUMENT获取文件Uri,加上FLAG_GRANT_WRITE_URI_PERMISSION权限,再打开输出流写入。
方案2:MANAGE_EXTERNAL_STORAGE特殊权限
和读取的方案2一致,获得权限后用File类直接写入SD卡任意位置,同样仅适用于少数合规应用。
三、示例:开发文件浏览+查看/打开应用
核心流程如下:
- 唤起SAF选择目录:启动
ACTION_OPEN_DOCUMENT_TREE让用户选择SD卡上的目标文件夹,持久化权限。 - 遍历文件夹文件:用
DocumentFile.fromTreeUri获取目录对象,调用listFiles()遍历所有文件/子目录,展示文件名、大小、类型等信息。 - 查看文件内容:点击文件时,用
contentResolver.openInputStream(uri)读取文件内容,根据MIME类型选择TextView(文本、html)或其他控件展示。 - 打开文件:调用系统应用打开文件,用
ACTION_VIEWIntent:fun openFile(fileUri: Uri, mimeType: String?) { val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(fileUri, mimeType) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(intent) }
注意事项:
- 必须持久化SAF权限(
takePersistableUriPermission),否则应用重启后会丢失访问权。 - 处理不同MIME类型的文件时,要正确设置Intent的type参数,避免无法打开。
内容的提问来源于stack exchange,提问作者John Allsup
相关产品推荐
相关产品推荐

