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
相关产品推荐
相关产品推荐

