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

谷歌搜索了一下,最直接的解決方法是在代碼中動(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)存泄漏

這是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ù))