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

Android 8及以下MediaStore保存音频至VoiceChanger目录失败求助

Android 8及以下版本MediaStore保存音频失败的修复方案

你的代码在Android 10+正常工作,但低版本出问题主要是几个核心错误:

问题根源

  1. DATA字段赋值错误:低版本MediaStore.Audio.Media.DATA需要传入完整的文件路径(包含文件名),你只传了目录路径,导致MediaStore无法定位具体文件。
  2. getContentUri调用错误:低版本不需要通过字符串构建content uri,直接使用MediaStore.Audio.Media.EXTERNAL_CONTENT_URI即可,你的写法会导致uri格式错误。
  3. savedPath逻辑错误:你把原缓存文件路径赋值给了savedPath,而非实际保存到VoiceChanger目录的新文件路径。
  4. 缺少媒体库扫描:低版本写入文件后需要主动通知系统扫描,否则音乐应用无法立即看到新文件。

修复后的代码

suspend fun String.saveAudioToGallery(
    context: Context, mimeType: String = "audio/x-wav", done: (String?) -> Unit, error: () -> Unit
) = withContext(Dispatchers.IO) {
    var savedPath: String? = null
    val fileName = "Vc_${System.currentTimeMillis()}.wav"
    val contentValues = ContentValues().apply {
        put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Audio.Media.MIME_TYPE, mimeType)
        put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
        put(MediaStore.Audio.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000)
    }

    val collection: Uri
    val targetFile: File

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        contentValues.put(
            MediaStore.Audio.Media.RELATIVE_PATH,
            "${Environment.DIRECTORY_MUSIC}/VoiceChanger"
        )
        collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
        targetFile = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),
            "VoiceChanger/$fileName"
        )
    } else {
        val directory = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),
            "VoiceChanger"
        )
        if (!directory.exists()) {
            directory.mkdirs()
        }
        targetFile = File(directory, fileName)
        // 低版本必须传入完整文件路径到DATA字段
        contentValues.put(MediaStore.Audio.Media.DATA, targetFile.absolutePath)
        // 低版本直接使用EXTERNAL_CONTENT_URI
        collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    }

    val contentResolver = context.contentResolver

    try {
        val audioUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentResolver.insert(collection, contentValues)
        } else {
            // 低版本先创建空文件,再插入MediaStore
            if (targetFile.createNewFile()) {
                contentResolver.insert(collection, contentValues)
            } else {
                null
            }
        }

        Log.d("inputStram", "saveAudioToGalleryUri:$audioUri ")

        audioUri?.let { uri ->
            contentResolver.openOutputStream(uri)?.use { os ->
                val sourceFile = File(this@saveAudioToGallery)
                FileInputStream(sourceFile).use { inputStream ->
                    inputStream.copyTo(os)
                    savedPath = targetFile.absolutePath // 改为目标文件路径
                    Log.d("inputStram", "saveAudioToGallery success: $savedPath")
                }
            }

            // 低版本通知媒体库扫描新文件
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                MediaScannerConnection.scanFile(
                    context,
                    arrayOf(targetFile.absolutePath),
                    arrayOf(mimeType)
                ) { _, _ ->
                    Log.d("inputStram", "Media scanned successfully")
                }
            }
        }
    } catch (e: Exception) {
        Log.e("TAG", "saveAudioToGallery failed: ${e.message}", e)
    }

    withContext(Dispatchers.Main) {
        if (savedPath != null) {
            Log.d("TAGGing", "saveAudioToGallery completed: $savedPath")
            done(savedPath)
        } else {
            Toast.makeText(context.applicationContext, "Failed to save audio to gallery", Toast.LENGTH_SHORT).show()
            error()
        }
    }
}

关键修改说明

  • 统一文件名处理:抽离fileName变量,确保高低版本使用同一文件名,避免路径混乱。
  • 低版本DATA字段修复:传入targetFile.absolutePath完整文件路径,而非目录路径。
  • 纠正content uri获取:低版本直接使用MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,避免错误的uri构建。
  • 修正savedPath赋值:改为实际保存的目标文件路径,而非原缓存文件路径。
  • 添加媒体扫描:低版本调用MediaScannerConnection.scanFile通知系统更新媒体库,让音乐应用能立即识别新文件。
  • 优化流操作:使用use块自动管理流的关闭,避免手动关闭引发的异常。

另外,需在AndroidManifest.xml中添加对应权限:

<!-- 低版本需要外部存储写入权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

针对Android 6.0+到8.1的设备,还需动态申请WRITE_EXTERNAL_STORAGE权限。

内容的提问来源于stack exchange,提问作者Muhammad Awais Shan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 18:22:32