最近项目开发中需要用到 MediaPlayer 来播放声音文件, 在此之前是采用 Ringtone 类来实现, 考虑到 Ringtone 性能跟代码可控性最终还是使用 MediaPlayer 重新实现这个功能, 其中遇到了两个问题, 这里做个简单记录.
先看看 第一版 问题代码 :
public class SoundHelper {
private static MediaPlayer mediaPlayer;
public static void playSound(final Context context,
final String audioPath) {
Uri uri = Uri
.parse("file:///system/media/audio/" + audioPath);
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
if (uri != null) {
try {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.reset();
}
mediaPlayer.setDataSource(context, uri);
mediaPlayer
.setOnErrorListener(new
MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp,
int what, int extra) {
mp.reset();
mp.release();
mp = null;
return false;
}
});
mediaPlayer.setOnCompletionListener(new
MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(
MediaPlayer mp) {
mp.reset();
mp.release();
mp = null;
}
});
mediaPlayer.prepare();
mediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
经过多次测试, 一直无法播放系统中的声音文件, 最后参照 Android Developer 官网上的多媒体话题才发现, 使用 MediaPlayer 的步骤少了一步, 即设置播放声音源的类型, 即 :
MediaPlayer.setAudioStreamType()
方法
加上后, 第二版 代码经测试可正常播放系统声音文件 :
public class SoundHelper {
......
mediaPlayer.setDataSource(context, uri);
mediaPlayer
.setAudioStreamType(AudioManager.STREAM_SYSTEM);
......
}
更多 MediaPlayer 用法参照官网教程 :
测试第二版代码过程中发现, 初次播放声音一切正常, 但第二次调用该方法时就会抛出 IllegalStateException 异常, 异常抛出点为 :
mediaPlayer.isPlaying()
最初以为是程序逻辑问题, 回过头检查多次, 发现并无不妥, 思前想后, 僵了大半天, 怀疑是 MediaPlayer 资源未释放完全导致, 尝试在每次调用该函数前加入判断语句, 如以下 第三版 代码 :
public class SoundHelper {
......
Uri uri = Uri
.parse("file:///system/media/audio/" + audioPath);
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
......
}
在每次调用该函数时先判断 MediaPlayer 所指对象是否不为空, 是的话就释放资源并赋为 null, 后面再重新 new 一个新的 MediaPlayer 对象.
经测试, 发现以上代码解决了问题, 不再抛出 IllegalStateException 异常, 可正常多次调用播放系统文件声音, 仍不解, 明明设置了 MediaPlayer.setOnCompletionListener
并且在其回调方法 onCompletion()
中也显式释放了 MediaPlayer 的相关资源, 可为何在第二次调用该方法时会判断出 MediaPlayer 所指对象不为空呢 ?
仔细观察, 方法最前面判断逻辑的 MediaPlayer 引用是 mediaPlayer, 而 onCompletion()
方法中传过来的引用参数是 mp, 难道这两个引用所指对象不是同一个对象 ? 因为前面第三版的代码佐证了这种猜想.
官网搜寻一番无果, 到 Google 搜索, 大概有如下解释 :
Android使用MediaPlayer开发时抛IllegalStateException, 作者 : lovelease
大概意思是说, mp 调用释放资源, 赋为 null 等操作只是操作了 Native 层的 MediaPlayer 对象, 却并无释放上层 Java 的 MediaPlayer 占用的资源, 引用 mediaPlayer 所指对象也并无影响.
基于这种说法, 尝试去除 第三版 代码添加内容, 修改为 第四版 代码如下 :
public class SoundHelper {
private static MediaPlayer mediaPlayer;
public static void playSound(final Context context,
final String audioPath) {
Uri uri = Uri
.parse("file:///system/media/audio/" + audioPath);
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
if (uri != null) {
try {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.reset();
}
mediaPlayer.setDataSource(context, uri);
mediaPlayer
.setAudioStreamType(AudioManager.STREAM_SYSTEM);
mediaPlayer
.setOnErrorListener(new
MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp,
int what, int extra) {
mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer = null;
return false;
}
});
mediaPlayer.setOnCompletionListener(new
MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(
MediaPlayer mp) {
mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer = null;
}
});
mediaPlayer.prepare();
mediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
即不再使用回调函数传递过来的 mp 引用来释放资源, 直接使用自己声明的 mediaPlayer 引用来完成工作.
经多次测试, 发现也不再出现 IllegalStateException 异常, 播放系统声音文件一切正常.
经过一番折腾, 感觉 MediaPlayer 还是一个比较磨人的东西, 虽然不知此处理方式是否正确, 但对 IllegalStateException 这个异常也算了解了点, 而目前也暂只能以这种说法为指导进行开发, 后续有时间再深入源码一探究竟.