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導致的內存泄漏的問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章