解决内存泄漏之路
一、在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开始做的修复)