如何在Android共享内置/外置存储写入文件及Android10文件选择器适配
安卓共享存储根目录创建专属文件夹与SAF文件选择器规范适配方案
一、共享存储根目录创建应用专属文件夹(对齐WhatsApp存储逻辑)
你要实现/storage/emulated/0/MyApp/的目录效果,有两种符合官方规范的实现方案,都不需要使用已废弃的API:
方案1:申请所有文件访问权限(MANAGE_EXTERNAL_STORAGE)
这是WhatsApp等需要大量读写公共存储的应用通用方案,Android 11及以上生效:
- Manifest权限配置:
<!-- Android 11及以上所有文件访问权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" /> <!-- Android 10兼容配置 --> <application android:requestLegacyExternalStorage="true" ...> </application>
- 权限申请代码:
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) }
- 权限通过后直接创建目标目录即可:
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
相关产品推荐
相关产品推荐

