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

