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

Android 13通话录音应用存储权限异常问题咨询

解决Android 13通话录音存储权限问题

你的问题核心是Android 13(API 33+)的Scoped Storage存储模型变更,导致原有的外部存储访问逻辑失效。以下是针对性的解决方案:

问题根源

  • Android 8(API 26)及以下使用传统存储模型,WRITE_EXTERNAL_STORAGE权限可完全访问外部存储根目录
  • Android 13废弃了WRITE_EXTERNAL_STORAGE权限,禁止直接在外部存储根目录创建自定义文件夹,除非申请特殊权限;推荐使用应用专属存储目录(无需额外权限)

解决方案一:使用应用专属存储目录(推荐)

应用专属存储目录属于应用私有空间,卸载应用时自动清理,无需额外存储权限,完全符合Android规范。

1. 修改文件路径获取逻辑

在CallReceiver类中修改getRecordingFilePath方法,传入Context获取应用专属目录:

private fun getRecordingFilePath(context: Context): String {
    // 获取应用专属外部音频存储目录
    val parentDir = context.getExternalFilesDir(Environment.DIRECTORY_AUDIO)
    val directory = File(parentDir, "TestRecording")
    
    // 用mkdirs()确保多级目录都能创建
    if (!directory.exists()) {
        directory.mkdirs()
    }
    
    val filePath = File(directory, "testing_${System.currentTimeMillis()}.amr")
    return filePath.path
}

2. 调整录音启动方法

修改startRecording方法,传入Context给路径获取函数:

private fun startRecording(context: Context?) {
    if(context == null) return
    
    mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) MediaRecorder(context) else MediaRecorder()

    mediaRecorder?.apply {
        setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
        setOutputFormat(MediaRecorder.OutputFormat.AMR_NB)
        setOutputFile(getRecordingFilePath(context)) // 传入Context
        setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
    }

    try {
        mediaRecorder?.prepare()
        mediaRecorder?.start()
    } catch (e : IOException){
        Log.d("CallError", e.toString())
    }

    Log.d("CallRec", "Call Recording Start......")
}

3. 简化权限请求逻辑

在MainActivity中,移除对WRITE_EXTERNAL_STORAGE权限的请求(应用专属目录无需此权限):

private fun allowPermission() {
    val permissions = arrayOf(
        Manifest.permission.READ_PHONE_STATE,
        Manifest.permission.RECORD_AUDIO
    )
    ActivityCompat.requestPermissions(this, permissions, 2)
}

private fun hasPermission() : Boolean{
    val phoneGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
    val recordGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
    
    return phoneGranted && recordGranted
}

解决方案二:坚持使用外部存储根目录自定义文件夹

若必须在外部存储根目录创建TestRecording文件夹,需申请特殊的MANAGE_EXTERNAL_STORAGE权限。

1. 修改Manifest权限声明

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />

<!-- 旧版本存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"/>

<!-- Android 10+ 全存储访问权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30"
    tools:ignore="ScopedStorage"/>

2. 调整权限检查与请求逻辑

在MainActivity中修改权限相关方法:

private fun allowPermission() {
    val basePermissions = arrayOf(
        Manifest.permission.READ_PHONE_STATE,
        Manifest.permission.RECORD_AUDIO
    )
    
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 需要引导用户到设置开启全存储权限
        if (!Environment.isExternalStorageManager()) {
            val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
            startActivity(intent)
        }
    } else {
        // Android 9及以下请求传统存储权限
        ActivityCompat.requestPermissions(this, basePermissions + Manifest.permission.WRITE_EXTERNAL_STORAGE, 2)
    }
    
    ActivityCompat.requestPermissions(this, basePermissions, 2)
}

private fun hasPermission() : Boolean{
    val phoneGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
    val recordGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
    
    val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        Environment.isExternalStorageManager()
    } else {
        ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }
    
    return phoneGranted && recordGranted && storageGranted
}

3. 修复目录创建逻辑

将getRecordingFilePath中的mkdir()改为mkdirs(),确保父目录不存在时也能创建:

private fun getRecordingFilePath(): String {
    val directory = File(Environment.getExternalStorageDirectory(), "TestRecording")
    if(!directory.exists()){
        directory.mkdirs() // 替换mkdir()为mkdirs()
    }
    val filePath = File(directory, "testing_${System.currentTimeMillis()}.amr")
    return filePath.path
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 20:59:50