VideoView导致内存泄漏

今天调试app的时候,LeakCanary提示开机视频页面SplashVideoActivity出现内存泄漏。然后用Android Profiler查看了一下,果然已经执行了finishSplashVideoActivity还存在于内存中。其中罪魁祸首就是这个AudioManager,它持有了SplashVideoActivity的引用。
这里写图片描述


然后去网上查了一下VideoView导致内存泄漏的问题,果然,原来是VideoView自身的bug。有人在Google的 Issue Tracker上提出了这个问题 Memory leak: VideoView prevents its activity from being GC’ed

导致这个问题的原因是 AudioManager可能会长时间持有Context,当使用者(这里即VideoView)请求了音频的焦点却没有及时释放的时候。

大家提出了各自的规避方法,主要有两种方法,但原理都是一样,即让AudioManager持有ApplicationContext,而不是持有Activity


方法一:在Java代码中初始化VideoView

VideoView videoView = new VideoView(getApplicationContext());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
videoView.setLayoutParams(params);
((RelativeLayout)findViewById(R.id.videoContainer)).addView(videoView, 0);

如果把VideoView写在Activity的布局文件中,初始化的时候自然是用ActivityContext,这里直接用代码初始化VideoView,强行传ApplicationContext,就避免了Activity被持有可能导致的内存泄漏。

方法二:重写getSystemService方法

考虑到AudioManagerVideoView里面的初始化方法如下:

    public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        ...
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        ...
    }

即它是通过调用ContextgetSystemService(Context.AUDIO_SERVICE)方法初始化的,那么不妨在Activity中重写这个方法,使用ApplicationContext来调用它,如下:

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(new ContextWrapper(newBase){
            @Override
            public Object getSystemService(String name) {
                if(Context.AUDIO_SERVICE.equals(name)){
                    return getApplicationContext().getSystemService(name);
                }
                return super.getSystemService(name);
            }
        });
    }

这样AudioManager将持有ApplicationContext而不是Activity


然后有大神说,以上的方法都不完美,因为真正的问题在于VideoView没有释放音频焦点而导致AudioManager没有及时释放Context,而不在于传了Activity。而且,如果按第一种方法用ApplicationContext去初始化VideoView,会存在一个隐患。因为如果VideoView播放视频文件出现解码错误的时候,会弹出一个提示框AlertDialog。而弹AlertDialog需要ActivityContext,如果用ApplicationContext去创建AlertDialog,将直接导致crash! 一言惊醒梦中人,对于大神,我只能大写加粗一个 字。对于第二种方法,大神说因为AudioManager只是Context的一个成员变量,如果通过ApplicationContext去获取将会获取到错误的实例,这里我不是特别明白。


最后官方修复了这个bug (时间点为2015年3月),主要修复了两个地方,一是在VideoView中及时释放音频焦点,二是让AudioManager持有ApplicationContext而不是持有Context(由此看来,上面第二种方法似乎可行),具体修复内容可以看 Fix context leak


而我的手机出现内存泄漏,可能是因为手机的版本比较低(Android 5.1)。根据修复的时间点,Android 6.0及之后的版本应该已经没有这个问题了,我对比了各版本的源码,自api 23之后就已经修复了此bug。如果要避免低版本手机出现此问题,我觉得可以用上面介绍的第二种方法。
经过测试: Android5.1版本的手机会出现此内存泄漏问题,Android7.0版本的手机没有。如果使用上述第二种方法,Android5.1版本的手机也不会出现此内存泄漏问题。

参考:关于Android VideoView导致的内存泄漏的问题

猜你喜欢

转载自blog.csdn.net/chenrenxiang/article/details/80634330