Android录音应用无法请求WRITE_EXTERNAL_STORAGE权限排查求助
问题分析与修复方案
一、WRITE_EXTERNAL_STORAGE权限请求无效的核心原因
你的代码中录音文件存储路径用的是getExternalFilesDir(Environment.DIRECTORY_MUSIC),这是应用专属外部存储目录:
- 从Android 10(API 29)开始,访问该目录不需要
WRITE_EXTERNAL_STORAGE权限,系统自动授予应用读写自己专属目录的权限 - Android 11+(API 30)及以上,
WRITE_EXTERNAL_STORAGE权限的作用被大幅限制,仅能访问公共目录,且申请时不会弹出授权弹窗(除非用户手动在设置中开启)
所以你请求这个权限完全是多余的,系统不会响应你的授权请求。
对应修复:
- 删除代码中所有关于
WRITE_EXTERNAL_STORAGE权限的检查、请求逻辑(包括checkWriteExternalStoragePermission、requestWriteExternalStoragePermission方法,以及onRequestPermissionsResult中对应的分支) - 修改Manifest中的权限配置,给
WRITE_EXTERNAL_STORAGE加上maxSdkVersion="28",因为只有Android 9及以下才需要这个权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
二、代码中的其他问题及修复
1. MediaRecorder资源泄漏风险
当前代码中,如果recorder.prepare()抛出IOException,recorder对象已经创建但未被释放,会导致资源泄漏。
修复:
在startRecording的catch块中添加recorder释放逻辑:
try { recorder.prepare(); recorder.start(); // ... 原有逻辑 } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "Recording failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); // 释放未成功启动的recorder if (recorder != null) { recorder.release(); recorder = null; } }
2. 权限请求逻辑不完善
当用户同意RECORD_AUDIO权限后,代码直接检查WRITE权限,若未授权就提示拒绝,但没有发起权限请求。另外,Android 6+如果用户之前拒绝过权限,应该先向用户说明权限用途,再发起请求。
修复:
修改requestRecordAudioPermission方法,添加权限说明逻辑:
private void requestRecordAudioPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) { Toast.makeText(this, "需要录音权限才能录制音频", Toast.LENGTH_LONG).show(); } ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_CODE ); }
同时修改onRequestPermissionsResult中RECORD_AUDIO分支的逻辑,若WRITE权限未授权(仅针对API 28及以下),则发起请求:
if (requestCode == RECORD_AUDIO_PERMISSION_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 仅针对Android 9及以下检查WRITE权限 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { startRecording(); } else { requestWriteExternalStoragePermission(); } } else { startRecording(); } } else { Toast.makeText(this, "Record audio permission denied", Toast.LENGTH_SHORT).show(); } }
3. 文件路径拼接不规范
直接用+ "/"拼接路径可能导致不同系统下的路径分隔符错误,建议用File构造方法处理:
private String getOutputFilePath() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); File audioVoiceDir = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "AudioVoice"); if (!audioVoiceDir.exists()) { boolean mkdirsResult = audioVoiceDir.mkdirs(); if (!mkdirsResult) { Log.e(TAG, "Failed to create directory: " + audioVoiceDir.getAbsolutePath()); } } // 用File构造方法拼接文件名 File outputFile = new File(audioVoiceDir, "Recording_" + timeStamp + ".3gp"); return outputFile.getAbsolutePath(); }
4. 配置变更时状态丢失
当屏幕旋转等配置变更发生时,Activity会重建,按钮状态、录音状态会丢失。需要通过onSaveInstanceState保存状态:
@Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("isRecording", isRecording); outState.putString("outputFile", outputFile); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); isRecording = savedInstanceState.getBoolean("isRecording"); outputFile = savedInstanceState.getString("outputFile"); // 恢复按钮状态 if (isRecording) { startButton.setVisibility(View.INVISIBLE); pauseButton.setVisibility(View.VISIBLE); } else { startButton.setVisibility(View.VISIBLE); pauseButton.setVisibility(View.INVISIBLE); } }
5. 按钮点击逻辑命名错误
pauseButton的点击事件绑定的是stopRecording()方法,按钮名称是pause,但实际执行的是停止录音,逻辑不符,建议修改按钮文本或方法名,避免混淆。
内容的提问来源于stack exchange,提问作者Aashutosh Sharan
相关产品推荐
相关产品推荐

