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

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权限的作用被大幅限制,仅能访问公共目录,且申请时不会弹出授权弹窗(除非用户手动在设置中开启)

所以你请求这个权限完全是多余的,系统不会响应你的授权请求。

对应修复:

  1. 删除代码中所有关于WRITE_EXTERNAL_STORAGE权限的检查、请求逻辑(包括checkWriteExternalStoragePermission、requestWriteExternalStoragePermission方法,以及onRequestPermissionsResult中对应的分支)
  2. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:47:09