最近项目开发中需要用到 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 用法参照官网教程 :
Android Developer 官网中 关于 MediaPlayer 的使用话题 (科学上网)Android Developer 国内官网中 关于 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 这个异常也算了解了点, 而目前也暂只能以这种说法为指导进行开发, 后续有时间再深入源码一探究竟.
