Android 11及以上:Downloads文件夹内创建子文件夹并读写问题排查
Android 11+ 在Downloads文件夹创建子文件夹并实现读写
原代码问题分析
你的代码无法创建文件夹的核心原因有两个:
DocumentsContract.EXTRA_INITIAL_URI要求传入Uri类型参数,但你传入的是File对象,这会导致系统无法正确识别初始目录,创建对话框可能未定位到Downloads目录,自然无法完成创建。- Android 11+(API 30及以上)对公共存储的直接文件访问做了严格限制,
Environment.getExternalStoragePublicDirectory()这类File API在公共目录下的操作已不再推荐,必须使用**存储访问框架(SAF)**来完成交互。
正确实现方案
1. 创建Downloads子文件夹(SAF方式)
使用ACTION_CREATE_DOCUMENT并指定正确的MIME类型,同时通过MediaStore获取Downloads目录的Uri作为初始路径:
// 定义请求码(若使用startActivityForResult) private const val REQUEST_CREATE_FOLDER = 1001 private fun createDownloadsSubfolder() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) // 指定创建文件夹的MIME类型 type = "vnd.android.document/directory" // 设置文件夹名称 putExtra(Intent.EXTRA_TITLE, "new folder") // 获取Downloads目录的Uri,作为初始打开路径 val downloadsUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri) } startActivityForResult(intent, REQUEST_CREATE_FOLDER) }
2. 处理创建结果(获取文件夹Uri)
在onActivityResult中获取创建后的文件夹Uri,后续读写操作都基于这个Uri:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CREATE_FOLDER && resultCode == RESULT_OK) { data?.data?.let { folderUri -> // 文件夹创建成功,保存Uri用于后续读写 writeFileToSubfolder(folderUri, "test.txt", "Hello from Downloads subfolder!") } } }
3. 向子文件夹写入文件
通过SAF的ContentResolver进行写入操作,无需额外权限:
private const val REQUEST_CREATE_FILE = 1002 private fun writeFileToSubfolder(folderUri: Uri, fileName: String, content: String) { // 创建文件的Intent,指定父文件夹Uri val createFileIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/plain" putExtra(Intent.EXTRA_TITLE, fileName) // 指定文件要创建在目标子文件夹中 putExtra(DocumentsContract.EXTRA_INITIAL_URI, folderUri) } startActivityForResult(createFileIntent, REQUEST_CREATE_FILE) } // 处理文件创建结果并写入内容 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CREATE_FILE && resultCode == RESULT_OK) { data?.data?.let { fileUri -> contentResolver.openOutputStream(fileUri)?.use { outputStream -> outputStream.write(content.toByteArray()) } } } }
4. (推荐)使用AndroidX Activity Result API
由于startActivityForResult已过时,更推荐使用registerForActivityResult:
private val createFolderLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { result.data?.data?.let { folderUri -> // 文件夹创建成功,执行后续操作 writeFileToSubfolder(folderUri, "test.txt", "Hello from Downloads subfolder!") } } } private val createFileLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { result.data?.data?.let { fileUri -> contentResolver.openOutputStream(fileUri)?.use { it.write("Hello from Downloads subfolder!".toByteArray()) } } } } private fun createDownloadsSubfolder() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "vnd.android.document/directory" putExtra(Intent.EXTRA_TITLE, "new folder") val downloadsUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri) } createFolderLauncher.launch(intent) } private fun writeFileToSubfolder(folderUri: Uri, fileName: String, content: String) { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/plain" putExtra(Intent.EXTRA_TITLE, fileName) putExtra(DocumentsContract.EXTRA_INITIAL_URI, folderUri) } createFileLauncher.launch(intent) }
关键说明
- 权限:Android 11+下使用SAF无需申请
READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE,所有操作通过用户授权的Uri完成。 - Uri持久化:如果需要长期访问创建的文件夹,可通过
ContentResolver.takePersistableUriPermission()获取持久权限,避免每次重启后丢失访问权限。
内容的提问来源于stack exchange,提问作者Khaled hawwas
相关产品推荐
相关产品推荐

