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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 03:13:20