当前位置: 首页>>编程语言>>正文


内存回收之Android MediaPlayer音频播放异常中断分析

yangfan 编程语言 , , , 去评论

MediaPlayer是android官方提供的一组音频/视频播放Api, 可以满足简单的、大部分音频/视频文件的播放需求。由于最近项目需要音频播放,第一次在Android APP引入了MediaPlayer。初步开发实现过程还算顺利,现在最终上线测试过程中,发现音频播放大概率出现中断不播完的问题,但是有时候又正常。经过反复的调试与问题追踪,最终发现每次播放中断,日志都会输出以下内容:

04-06 15:56:14.713 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released
04-06 15:56:35.819 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released

从日志内容可以看出,MediaPlayer jni底层调用出现了警告信息,大概意思是:MediaPlayer资源没有释放就结束了。为了进一步分析问题原因,在尝试各种方案和查询官方和stackoverflow论坛资料后,终于发现有价值的解决方案。以下引用自stackoverflow

I think this is because you create the media player within the scope of the method, therefore, when the method completes, it goes out of scope. This means there are no references, so is ok for garbage collection.
This means, it can be free'd by the GC before it has even called onCompletion, hence won't release before cleared. Instead, you need to store a reference to the media player as a member variable in your class.

stackoverflow有人提出了相同的播放中断问题:MediaPlayer finalized without being released,根据答题者@T. Kiley的回答,大部分人都解决了这个问题。@T.kiley的大概意思是:

当在方法内部创建MediaPlayer对象实例(局部变量)时,一旦方法执行完成,局部变量将不再被引用所持有。而根据Java JVM内存回收机制,不再被引用持有的对象,执行gc的
时候该对象就会被回收。这意味着,MediaPlayer对象实例在播放完成、释放之前是可以被gc回收的,因此会导致音频/视频播放中断。解决方案是:应当将MediaPlayer对象实例
声明为class类成员变量,而不是声明为方法的局部变量。

另外,Android MediaPlayer官方文档说明如下:

It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether. Once the MediaPlayer object is in the End state, it can no longer be used and there is no way to bring it back to any other state.

含义如下:

推荐在MediaPlayer对象在不再使用的时候,应当立即调用release()方法以便于所有和MediaPlayer关联的内部播放引擎资源能及时释放。资源可能包含单例资源,比如硬件
加速组件;release()调用失败可能会导致MediaPlayer后续的对象实例回退软件实现或者执行一起失败。一旦MediaPlayer对象处在结束状态,该对象就不能被使用,也没有
任何方法回到任何其他状态。

虽然根据以上方法,将MediaPlayer局部变量修改为类成员后,项目问题已经修复,测试也未发现问题;但是为了验证此问题是否由于局部变量引起,本着严谨的学习研究态度,写了一段测试代码进一步证实了这个问题的原因:

1.局部变量实现
# 为了简洁便于阅读,省略大部分结构性代码,只保留核心部分
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onPostResume() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                player();
            }
        }).start();
    }

    private void player(){
        MediaPlayer player = MediaPlayer.create(getApplicationContext(), R.raw.xxx_mp3);
        player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.start();
            }
        });
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                MediaPlayer.release();
            }
        });

        player.start();
    }
}

通过Android Studio连接真机调试,利用Android Studio monitor窗口Initiate GC操作主动回收内存,操作界面如下:

image

真机运行demo代码,每次点击Initiate GC时,播放就会停止,日志输出如下日志:

04-06 15:56:35.819 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released
2.类成员变量实现
# 为了简洁便于阅读,省略大部分结构性代码,只保留核心部分
public class MainActivity extends AppCompatActivity {

    private MediaPlayer player;
    @Override
    protected void onPostResume() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                player2();
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();

        if (player != null) {
            player.release();
        }
    }

    private void player2(){
        player = MediaPlayer.create(getApplicationContext(), R.raw.liren);
        player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.start();
            }
        });
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mediaPlayer.release();
            }
        });

        player.start();
    }
}

同样,和局部变量实现的调试方法一样,使用Android Studio monitor窗口Initiate GC操作主动回收内存,无论点击多少次回收操作,音频播放也没有中断,日志业务警告日志输出。至此,通过前后2段demo代码运行结果的对比,说明MediaPlayer声明为类成员变量的方案更好,实际应用中运行更稳定。#在挖坑填坑的路上越走越远,希望对你有所帮助#。

Android MediaPlayer

参考资料:
1. http://stackoverflow.com/questions/15023037/garbage-collection-causes-mediaplayer-finalized-without-being-released
2. https://developer.android.com/reference/android/media/MediaPlayer.html
本文由《纯净的天空》出品。文章地址: https://vimsky.com/article/3303.html,未经允许,请勿转载。