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

Kotlin Android 创建多级目录求助:在Podcasts目录下构建风电场相关文件夹结构

在Android的Podcasts目录下创建多层子文件夹的解决方案

作为Android新手,你遇到的问题核心在于错误的目录路径写法和Android不同版本的存储权限/机制差异,直接硬编码/Podcasts/Fuchsberg/肯定是行不通的——外部存储的根目录不是/,而且Android 10+引入了Scoped Storage,限制了直接访问公共目录的方式。下面一步步帮你解决:

一、修正目录获取逻辑(核心问题)

你不能直接写死路径,得用系统提供的API获取公共Podcasts目录的正确位置,再拼接子文件夹。这里分版本适配:

private fun createFolder(windparkName: String, anlageNumber: String) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android 11+ 用MediaStore适配Scoped Storage
        createFolderForAndroid11Plus(windparkName, anlageNumber)
    } else {
        // Android 10及以下用旧方法
        val podcastsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS)
        // 拼接成你要的结构:Podcasts/风电场名称/风机编号
        val targetDir = File(podcastsDir, "$windparkName/$anlageNumber")
        
        // mkdirs()会自动创建所有不存在的父目录
        val isCreated = targetDir.mkdirs()
        if (isCreated) {
            Toast.makeText(this, "目录创建成功:${targetDir.path}", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "目录创建失败,请检查权限或路径", Toast.LENGTH_SHORT).show()
        }
    }
}

// Android 11+ 专属的目录创建方法(适配Scoped Storage)
private fun createFolderForAndroid11Plus(windparkName: String, anlageNumber: String) {
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, anlageNumber)
        // 设置相对路径,指向Podcasts下的子目录
        put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_PODCASTS}/$windparkName")
        put(MediaStore.MediaColumns.MIME_TYPE, "vnd.android.document/directory")
        put(MediaStore.MediaColumns.IS_PENDING, 1)
    }

    val uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
    uri?.let {
        contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
        contentResolver.update(it, contentValues, null, null)
        Toast.makeText(this, "目录创建成功", Toast.LENGTH_SHORT).show()
    } ?: run {
        Toast.makeText(this, "目录创建失败", Toast.LENGTH_SHORT).show()
    }
}

二、处理动态权限申请(必须步骤)

你在Manifest里加了权限,但Android 6.0(API 23)及以上需要动态申请危险权限,否则目录创建会失败。

1. 定义权限相关变量

private val REQUEST_PERMISSION_CODE = 100

// 根据系统版本返回需要申请的权限
private fun getRequiredPermissions(): Array<String> {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        // Android 13+ 用细分的媒体权限
        arrayOf(
            Manifest.permission.READ_MEDIA_AUDIO,
            Manifest.permission.RECORD_AUDIO
        )
    } else {
        // Android 12及以下用旧的存储权限
        arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.RECORD_AUDIO
        )
    }
}

2. 修改按钮点击逻辑,先检查权限

btnStart.setOnClickListener {
    val permissions = getRequiredPermissions()
    // 检查所有权限是否已授权
    val allPermissionsGranted = permissions.all {
        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
    }

    if (allPermissionsGranted) {
        // 获取输入的风电场名称和风机编号
        val windparkName = etWindpark.text.toString().trim()
        val anlageNumber = etAnlage.text.toString().trim()
        if (windparkName.isNotEmpty() && anlageNumber.isNotEmpty()) {
            createFolder(windparkName, anlageNumber)
        } else {
            Toast.makeText(this, "请填写完整的风电场名称和风机编号", Toast.LENGTH_SHORT).show()
        }
    } else {
        // 申请权限
        ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CODE)
    }
}

3. 处理权限申请结果

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_PERMISSION_CODE) {
        val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
        if (allGranted) {
            val windparkName = etWindpark.text.toString().trim()
            val anlageNumber = etAnlage.text.toString().trim()
            if (windparkName.isNotEmpty() && anlageNumber.isNotEmpty()) {
                createFolder(windparkName, anlageNumber)
            } else {
                Toast.makeText(this, "请填写完整的风电场名称和风机编号", Toast.LENGTH_SHORT).show()
            }
        } else {
            Toast.makeText(this, "需要授予存储和录音权限才能创建目录", Toast.LENGTH_SHORT).show()
        }
    }
}

三、更新Manifest权限(适配Android 13+)

把你原来的权限声明改成下面这样,适配不同版本的权限要求:

<!-- Android 13+ 的音频权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<!-- Android 12及以下的存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

<!-- 保持你原来的requestLegacyExternalStorage,适配Android 10 -->
<application
    ...
    android:requestLegacyExternalStorage="true">
    ...
</application>

几个关键注意点

  • 绝对路径不可靠:永远不要硬编码外部存储路径,用Environment类的方法获取系统目录。
  • Scoped Storage适配:Android 11+限制了直接操作公共目录,必须用MediaStore API来创建文件夹或文件。
  • 权限检查要彻底:不同Android版本的权限要求不一样,一定要根据版本动态适配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 07:52:47