VideoView的內存泄漏問題

解決內存泄漏之路

一、 在XML文件直接用VideoView控件時,很容易造成內存泄漏,最開始出現的內存泄漏如下
image.png

谷歌搜索了一下,最直接的解決方法是在代碼中動態創建VideoView,傳入的參數用Application

var mVideoView: VideoView? = null

if (mVideoView == null) {
     mVideoView = VideoView(MyApplication.appContext)
     video_view_container.addView(mVideoView, RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
}

二、 這麼修改後,還是會出現內存泄漏

image.png

這是VideoView的父View引起的,那麼解決方法是在onDestory的時候,父View移除VideoView

override fun onDestroy() {
    mVideoView = null
    video_view_container?.removeAllViews()
    super.onDestroy()
}

三、 爲了避免出現其他內存泄漏,在Activity的onDestory時候,釋放VideoView資源,置空listener

override fun onDestroy() {
  	mVideoView?.suspend()
    mVideoView?.setOnErrorListener(null)
    mVideoView?.setOnPreparedListener(null)
    mVideoView?.setOnCompletionListener(null)

    mVideoView = null
    video_view_container?.removeAllViews()
    super.onDestroy()
}

四、 以爲這樣就解決了VideoView的內存泄漏問題,但測着測着,竟然出現了崩潰,崩潰場景是視頻播放不了,準備彈窗的時候,崩潰如下:

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application

原因

創建VideoView,爲解決內存泄漏,傳入Application會有一個坑。VideoView在發生播放錯誤的時候,會有彈窗錯誤提示的Dialog,Dialog依賴傳入mContext,如果是Application,那麼會報崩潰。

解決方法

通過查看源碼發現,在彈這個dialog的時候,會有條件判斷,攔截這個條件不彈錯誤提示的Dialog即不會崩潰,然後這個Dialog在外部回調接口彈出。

這個攔截條件是VideoView的setOnErrorListener的實現方法返回true。

大功告成,既解決VideoView的內存泄漏,又解決了崩潰問題。

更進一步

雖然快速地解決了問題,但實際泄漏點的Root引用還沒有好好分析,到底是哪個Root引用導致的內存泄漏?

泄漏點的root引用是PlayerBase$1.this$0(PlayeBase的子類是MediaPlayer),這是IAppsCallback$Stub的匿名類,在7.0系統可以看到

http://androidxref.com/7.0.0_r1/xref/frameworks/base/media/java/android/media/PlayerBase.java

mAppOpsCallback = new IAppOpsCallback.Stub() {
    public void opChanged(int op, int uid, String packageName) {
       synchronized (mAppOpsLock) {
           if (op == AppOpsManager.OP_PLAY_AUDIO) {
               updateAppOpsPlayAudio_sync();
          }
       }
    }
};

這個匿名內部類是Binder類,長生命週期持有短生命週期VideoPlayerActivit的引用,導致VideoPlayerActivit的泄漏

再進一步看,發現8.0以上的手機不會出現這個內存泄漏,原來是系統源碼已經解決了這個內存泄漏

http://androidxref.com/8.0.0_r4/xref/frameworks/base/media/java/android/media/PlayerBase.java

處理方式是初始化mAppOpsCallback時,new 一個靜態內部類,並且這個靜態類傳入的參數是弱引用

mAppOpsCallback = new IAppOpsCallbackWrapper(this);
private static class IPlayerWrapper extends IPlayer.Stub {
        private final WeakReference<PlayerBase> mWeakPB;

        public IPlayerWrapper(PlayerBase pb) {
            mWeakPB = new WeakReference<PlayerBase>(pb);
       }
       ....
}

(從AOSP的提交記錄可以看出,這是8.0開始做的修復)

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