VideoView的內(nèi)存泄漏問(wèn)題

解決內(nèi)存泄漏之路

一、在XML文件直接用VideoView控件時(shí),很容易造成內(nèi)存泄漏,最開(kāi)始出現(xiàn)的內(nèi)存泄漏如下

image.png

谷歌搜索了一下,最直接的解決方法是在代碼中動(dòng)態(tài)創(chuàng)建VideoView,傳入的參數(shù)用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))
}

二、這么修改后,還是會(huì)出現(xiàn)內(nèi)存泄漏

image.png

這是VideoView的父View引起的,那么解決方法是在onDestory的時(shí)候,父View移除VideoView

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

三、為了避免出現(xiàn)其他內(nèi)存泄漏,在Activity的onDestory時(shí)候,釋放VideoView資源,置空l(shuí)istener

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

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

四、以為這樣就解決了VideoView的內(nèi)存泄漏問(wèn)題,但測(cè)著測(cè)著,竟然出現(xiàn)了崩潰,崩潰場(chǎng)景是視頻播放不了,準(zhǔn)備彈窗的時(shí)候,崩潰如下:

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

原因

創(chuàng)建VideoView,為解決內(nèi)存泄漏,傳入Application會(huì)有一個(gè)坑。VideoView在發(fā)生播放錯(cuò)誤的時(shí)候,會(huì)有彈窗錯(cuò)誤提示的Dialog,Dialog依賴傳入mContext,如果是Application,那么會(huì)報(bào)崩潰。

解決方法

通過(guò)查看源碼發(fā)現(xiàn),在彈這個(gè)dialog的時(shí)候,會(huì)有條件判斷,攔截這個(gè)條件不彈錯(cuò)誤提示的Dialog即不會(huì)崩潰,然后這個(gè)Dialog在外部回調(diào)接口彈出。

這個(gè)攔截條件是VideoView的setOnErrorListener的實(shí)現(xiàn)方法返回true。

大功告成,既解決VideoView的內(nèi)存泄漏,又解決了崩潰問(wèn)題。

更進(jìn)一步

雖然快速地解決了問(wèn)題,但實(shí)際泄漏點(diǎn)的Root引用還沒(méi)有好好分析,到底是哪個(gè)Root引用導(dǎo)致的內(nèi)存泄漏?

泄漏點(diǎn)的root引用是PlayerBase$1.this$0(PlayeBase的子類(lèi)是MediaPlayer),這是IAppsCallback$Stub的匿名類(lèi),在7.0系統(tǒng)可以看到

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();
          }
       }
    }
};

這個(gè)匿名內(nèi)部類(lèi)是Binder類(lèi),長(zhǎng)生命周期持有短生命周期VideoPlayerActivit的引用,導(dǎo)致VideoPlayerActivit的泄漏

再進(jìn)一步看,發(fā)現(xiàn)8.0以上的手機(jī)不會(huì)出現(xiàn)這個(gè)內(nèi)存泄漏,原來(lái)是系統(tǒng)源碼已經(jīng)解決了這個(gè)內(nèi)存泄漏

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

處理方式是初始化mAppOpsCallback時(shí),new 一個(gè)靜態(tài)內(nèi)部類(lèi),并且這個(gè)靜態(tài)類(lèi)傳入的參數(shù)是弱引用

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開(kāi)始做的修復(fù))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容