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

集成RecordView实现语音消息时,点击录音按钮后RecordView不显示且报setAudioSource错误如何解决

问题根因

日志中的setAudioSource failed崩溃,本质是两个核心问题叠加导致的:

  • 未动态授予录音权限:Android 6.0之后,RECORD_AUDIO属于危险权限,仅在Manifest声明无法直接调用麦克风接口,没有拿到授权时初始化音频源就会直接抛出运行时异常
  • 代码逻辑顺序混乱:将RecordView的监听器绑定、MediaRecorder初始化逻辑全部塞到录音按钮的点击回调中,不仅导致RecordView无法在首次点击时正常弹出,还容易出现重复绑定、状态错乱的问题
具体修复步骤

1. 补全权限申请逻辑

首先在AndroidManifest.xml中声明所需的基础权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

不要等用户点击录音按钮才申请权限,需要在页面初始化阶段(onCreate中)就做动态权限判断,提前向用户申请授权:

private static final int REQUEST_RECORD_PERMISSION = 100;
private final String[] requiredPermissions = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

private void checkPermission() {
    boolean needRequest = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
            || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
    if (needRequest) {
        ActivityCompat.requestPermissions(this, requiredPermissions, REQUEST_RECORD_PERMISSION);
    }
}

在initView()执行前调用checkPermission(),确保用户进入页面时就完成权限授予,避免首次点击录音直接崩溃。

2. 修正代码逻辑顺序,避免状态混乱

原代码将RecordView的监听绑定逻辑写在按钮的点击回调中,会导致首次点击时才完成监听绑定,RecordView根本无法响应触控事件正常弹出。需要调整为页面初始化时一次性完成所有绑定逻辑,核心调整点:

  • 提前关联RecordButton和RecordView,一次性绑定所有录音回调
  • 抽离重复的MediaRecorder释放逻辑,避免内存泄漏和未释放实例导致的初始化崩溃
  • 适配高版本安卓的分区存储规则,减少权限适配问题

修正后的核心实现代码:

private void initView() {
    // 提前关联RecordButton与RecordView
    recordBtn.setRecordView(recordView);
    recordBtn.setListenForRecord(true);

    // 页面初始化阶段就绑定所有录音回调,不要放在点击事件里
    recordView.setOnRecordListener(new OnRecordListener() {
        @Override
        public void onStart() {
            Log.d("RecordView", "onStart");
            setUpRecording();
            try {
                mediaRecorder.prepare();
                mediaRecorder.start();
            } catch (IOException e) {
                e.printStackTrace();
                // 初始化失败时主动重置RecordView状态,避免界面卡死
                recordView.resetUI();
            }
        }

        @Override
        public void onCancel() {
            Log.d("RecordView", "onCancel");
            releaseMediaRecorder();
            File file = new File(recordPath);
            if (file.exists()) file.delete();
        }

        @Override
        public void onFinish(long recordTime) {
            Log.d("RecordView", "onFinish");
            releaseMediaRecorder();
            // 录音时长不足1秒时自动删除临时文件
            if (recordTime < 1000) {
                File file = new File(recordPath);
                if (file.exists()) file.delete();
                Toast.makeText(MessageActivity.this, "录音时长过短", Toast.LENGTH_SHORT).show();
                return;
            }
            sendRecordingMessage(recordPath);
        }

        @Override
        public void onLessThanSecond() {
            Log.d("RecordView", "onLessThanSecond");
            releaseMediaRecorder();
            File file = new File(recordPath);
            if (file.exists()) file.delete();
        }
    });

    // 按钮点击的Toast提示保留即可,不要再塞入初始化逻辑
    recordBtn.setOnRecordClickListener(v -> {
        Toast.makeText(MessageActivity.this, "RECORD BUTTON CLICKED", Toast.LENGTH_SHORT).show();
        Log.d("RecordButton","RECORD BUTTON CLICKED");
    });
}

// 统一处理MediaRecorder释放逻辑
private void releaseMediaRecorder() {
    if (mediaRecorder == null) return;
    try {
        mediaRecorder.stop();
    } catch (RuntimeException e) {
        // 捕获无效录音导致的stop异常
        e.printStackTrace();
    }
    mediaRecorder.release();
    mediaRecorder = null;
}

private void setUpRecording() {
    // 每次录音重新初始化MediaRecorder,避免复用未释放的实例
    mediaRecorder = new MediaRecorder();
    // 二次校验权限,无权限时直接中断初始化
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
        Toast.makeText(this, "无录音权限,无法开始录音", Toast.LENGTH_SHORT).show();
        return;
    }
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

    // 存应用私有目录,适配高版本安卓分区存储,无需额外申请存储权限
    File saveDir = new File(getExternalFilesDir("chats"), "");
    if (!saveDir.exists()) saveDir.mkdirs();
    recordPath = saveDir.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".3gp";
    mediaRecorder.setOutputFile(recordPath);
}

3. 注意事项

  • 不需要手动控制RecordView的Visibility属性,库本身会在录音开始、取消、结束时自动控制弹窗的显示隐藏,手动设置反而会和库的内部状态冲突
  • 存储路径替换为应用私有目录后,不需要适配公共外部存储的复杂权限规则,可大幅降低高版本安卓的读写异常
  • 必须给MediaRecorder的stop()方法加异常捕获,当录音时长过短没有有效音频文件时,直接调用stop会抛出运行时异常导致崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.31 11:06:09