解決內存泄漏之路
一、 在XML文件直接用VideoView控件時,很容易造成內存泄漏,最開始出現的內存泄漏如下
谷歌搜索了一下,最直接的解決方法是在代碼中動態創建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))
}
二、 這麼修改後,還是會出現內存泄漏
這是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開始做的修復)