Android 编程技巧之 ----- MediaPlayer 问题简记

  • 前言
  • 问题一 播放系统文件无声
    • 知识延伸 推荐链接
  • 问题二 IllegalStateException 异常
  • 结语

前言

最近项目开发中需要用到 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 异常

测试第二版代码过程中发现, 初次播放声音一切正常, 但第二次调用该方法时就会抛出 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 这个异常也算了解了点, 而目前也暂只能以这种说法为指导进行开发, 后续有时间再深入源码一探究竟.

你可能感兴趣的:(Android)