这两个在集成环信实现IM功能,在开发过程中遇到一个问题,发送文字、表情、图片、位置都是正常的,但是语音就是一直发送不成功,并且只是在第一次安装app登录账号之后发送语音的时候才会出现发送不成功的问题,如果把app杀掉重新打开或者退出账号重新登录再发送语音就能成功发送了。一开始以为是某些初始化的代码没有加,但是仔细查看该加的初始化的东西都加了,没办法,问题要解决啊,于是开始一行一行的看环信的代码,最开始单点到ACTION_UP的时候发现调用recorder.stop()方法的时候会一直报IllegalStateException,显示在这句话这里找原因,一直没找到解决办法。后来开始转换思路,既然recorder.stop()方法会出现问题,肯定是创建的时候哪里出了问题,然后开始在ACTION_DOWN的地方找原因,最后发现是环信给的代码里有一个文件创建的问题,导致文件创建不成功,语音无法发送成功。在说这个问题之前先了解一下环信发送语音功能的实现过程。
环信的聊天页面的底部是一个自定义View,
![](https://my.csdn.net/my/album/detail/1836578)
![](https://my.csdn.net/my/album/detail/1836578)
![](https://img-my.csdn.net/uploads/201710/27/1509086130_5141.gif)
当点击“按住说话”按钮的时候会出发它的onTouch事件,
//发送语音按钮被点击的时候
buttonPressToSpeak.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (listener != null) {
return listener.onPressToSpeakBtnTouch(v, event);
}
return false;
}
});
}
然后这个listener会回调到聊天页面,EaseChatFragment,在这个页面处理它的touch事件,
/**
* 发送语音
* @param v
* @param event
* @return
*/
@Override
public boolean onPressToSpeakBtnTouch(View v, MotionEvent event) {
return voiceRecorderView.onPressToSpeakBtnTouch(v, event, new EaseVoiceRecorderCallback() {
@Override
public void onVoiceRecordComplete(String voiceFilePath, int voiceTimeLength) {
sendVoiceMessage(voiceFilePath, voiceTimeLength);
}
});
}
我们进入onPressSpeakBtnTouch()方法,会看到在这里面对touch的按下、移动、抬起手势进行了处理。
public boolean onPressToSpeakBtnTouch(View v, MotionEvent event, EaseVoiceRecorderCallback recorderCallback) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
try {
if (EaseChatRowVoicePlayClickListener.isPlaying)
EaseChatRowVoicePlayClickListener.currentPlayListener.stopPlayVoice();
v.setPressed(true);
startRecording();
} catch (Exception e) {
v.setPressed(false);
}
return true;
case MotionEvent.ACTION_MOVE:
if (event.getY() < 0) {
showReleaseToCancelHint();
} else {
showMoveUpToCancelHint();
}
return true;
case MotionEvent.ACTION_UP:
v.setPressed(false);
if (event.getY() < 0) {
// discard the recorded audio.
discardRecording();
} else {
// stop recording and send voice file
try {
int length = stopRecoding();
if (length > 0) {
if (recorderCallback != null) {
recorderCallback.onVoiceRecordComplete(getVoiceFilePath(), length);
}
} else if (length == EMError.FILE_INVALID) {
Toast.makeText(context, R.string.Recording_without_permission, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, R.string.The_recording_time_is_too_short, Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, R.string.send_failure_please, Toast.LENGTH_SHORT).show();
}
}
return true;
default:
discardRecording();
return false;
}
}
ACTION_DOWN的时候,开始录音,首先是先判断一下当前是否正处于录音状态,如果是,先关闭录音,重新录,然后开始录音,接下来就是录音的代码了:在录音的代码中,带删除线的那一行代码是环信原来自己的代码,红色标记的部分是我自己添加进去的,就是这里出的错,这里要执行的操作是创建对应的录音文件,但是创建一个文件的时候需要先创建这个文件对应的目录,这里没有创建目录的过程就直接创建文件,所以在执行接下来的语句的时候就会报no such file or directory错误。
public String startRecording(Context appContext) {
file = null;
try {
// need to create recorder every time, otherwise, will got exception
// from setOutputFile when try to reuse
if (recorder != null) {
recorder.release();
recorder = null;
}
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setAudioChannels(1); // MONO
recorder.setAudioSamplingRate(8000); // 8000Hz
recorder.setAudioEncodingBitRate(64); // seems if change this to
voiceFileName = getVoiceFileName(EMClient.getInstance().getCurrentUser());
voiceFilePath = PathUtil.getInstance().getVoicePath() + "/" + voiceFileName;
//file = new File(voiceFilePath);
File file2=PathUtil.getInstance().getVoicePath();
if(!file2.exists()){
file2.mkdirs();
}
file = new File(voiceFilePath);
if(!file.exists()){
file.createNewFile();
}
recorder.setOutputFile(file.getAbsolutePath());
recorder.prepare();
isRecording = true;
recorder.start();
} catch (IOException e) {
EMLog.e("voice", "prepare() failed");
}
new Thread(new Runnable() {
@Override
public void run() {
try {
while (isRecording) {
android.os.Message msg = new android.os.Message();
msg.what = recorder.getMaxAmplitude() * 13 / 0x7FFF;
handler.sendMessage(msg);
SystemClock.sleep(100);
}
} catch (Exception e) {
EMLog.e("voice", e.toString());
}
}
}).start();
startTime = new Date().getTime();
EMLog.d("voice", "start voice recording to file:" + file.getAbsolutePath());
return file == null ? null : file.getAbsolutePath();
}
ACTION_MOVE的时候,根据手指所在的位置执行不同的结果。
public void showReleaseToCancelHint() {
recordingHint.setText(context.getString(R.string.release_to_cancel));
recordingHint.setBackgroundResource(R.drawable.ease_recording_text_hint_bg);
}
public void showMoveUpToCancelHint() {
recordingHint.setText(context.getString(R.string.move_up_to_cancel));
recordingHint.setBackgroundColor(Color.TRANSPARENT);
}
ACTION_UP的时候,停止录音,保存语音文件。
public int stopRecoding() {
if(recorder != null){
isRecording = false;
recorder.stop();
recorder.release();
recorder = null;
if(file == null || !file.exists() || !file.isFile()){
return EMError.FILE_INVALID;
}
if (file.length() == 0) {
file.delete();
return EMError.FILE_INVALID;
}
int seconds = (int) (new Date().getTime() - startTime) / 1000;
EMLog.d("voice", "voice recording finished. seconds:" + seconds + " file length:" + file.length());
return seconds;
}
return 0;
}