You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android共享内置/外置存储写入文件及Android10文件选择器适配

安卓共享存储根目录创建专属文件夹与SAF文件选择器规范适配方案

一、共享存储根目录创建应用专属文件夹(对齐WhatsApp存储逻辑)

你要实现/storage/emulated/0/MyApp/的目录效果,有两种符合官方规范的实现方案,都不需要使用已废弃的API:

方案1:申请所有文件访问权限(MANAGE_EXTERNAL_STORAGE)

这是WhatsApp等需要大量读写公共存储的应用通用方案,Android 11及以上生效:

  1. Manifest权限配置:
<!-- Android 11及以上所有文件访问权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30" />
<!-- Android 10兼容配置 -->
<application
    android:requestLegacyExternalStorage="true"
    ...>
</application>
  1. 权限申请代码:
val RC_MANAGE_STORAGE = 1001
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
    // 跳转系统设置页引导用户开启权限
    val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
    intent.data = Uri.parse("package:${packageName}")
    startActivityForResult(intent, RC_MANAGE_STORAGE)
}
  1. 权限通过后直接创建目标目录即可:
val myAppDir = File(Environment.getExternalStorageDirectory(), "MyApp")
if (!myAppDir.exists()) myAppDir.mkdirs()
// 后续可直接在该目录下创建视频文件传给FFmpeg使用

注:Google Play上架时需要说明该权限的使用场景,视频录制类应用符合审核规则,不会被驳回。

方案2:SAF目录授权(无需特殊权限)

如果不想申请MANAGE_EXTERNAL_STORAGE权限,可通过系统文件选择器让用户主动授权根目录访问权限:

val RC_CHOOSE_ROOT = 1002
// 唤起目录选择器
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, RC_CHOOSE_ROOT)

// 处理授权结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == RC_CHOOSE_ROOT && resultCode == Activity.RESULT_OK) {
        data?.data?.let { rootUri ->
            // 申请持久化权限,下次启动应用无需重复授权
            contentResolver.takePersistableUriPermission(
                rootUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
            val rootDir = DocumentFile.fromTreeUri(this, rootUri)
            // 创建MyApp专属目录
            val myAppDir = rootDir?.findFile("MyApp") ?: rootDir?.createDirectory("MyApp")
            // 后续可在myAppDir下创建、读写文件
        }
    }
}

二、SAF选择txt文件Android10唤起失败适配

你原有代码唤起失败的核心原因是缺少CATEGORY_OPENABLE类别,这是ACTION_OPEN_DOCUMENT强制要求的配置,修正后的代码如下:

val RC_SELECT_TXT = 1003
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
    // 必须添加该类别才能匹配系统文件选择器
    addCategory(Intent.CATEGORY_OPENABLE)
    // 仅筛选txt文件,不需要使用*/*通配符
    type = "text/plain"
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
            Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
}
startActivityForResult(Intent.createChooser(intent, "选择txt文件"), RC_SELECT_TXT)

读取选中的txt文件内容示例:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == RC_SELECT_TXT && resultCode == Activity.RESULT_OK) {
        data?.data?.let { uri ->
            // 持久化权限,后续无需重复选择即可访问该文件
            contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
            // 读取文件内容
            contentResolver.openInputStream(uri)?.use { inputStream ->
                val txtContent = inputStream.bufferedReader().use { it.readText() }
                // 业务逻辑处理
            }
        }
    }
}

注:该适配方案不需要添加requestLegacyExternalStorage标识,也不需要申请存储权限,完全符合官方SAF规范。

内容的提问来源于stack exchange,提问作者ShaRy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 12:24:04