eclipse memory analyzer(MAT) 让 Bug 无处藏身

来源:互联网 发布:下载360云端软件下载 编辑:程序博客网 时间:2024/05/21 09:28

这段时间在做项目的时候,为了更好的用户体验,在 https://github.com/wasabeef/awesome-android-ui 用了一些现成的控件。其实有些控件本身是有 Bug 的,不过还好有 eclipse memory analyzer(MAT) 的帮助,解决这些现在控件的 Bug 不是太困难的事情。

关于 MAT 的基本概念,如有时间再写一篇文章详述。这里用一个分析实例来记录下如何利用它来找到 Bug。就以 AndroidImageSlider 为例吧,它也是个开源控件,能实现图片轮播,而且还有各种特效。其地址为 https://github.com/daimajia/AndroidImageSlider。

笔者在刚开始使用 ImageSlider 的时候甚为高兴,太方便了,而且很酷。但是它也给笔者带来了个问题:内存泄露,在反复打开关闭这个控件相关的 Activity 的时候,没多久,就报 OOM 了。怎么办呢?下面一一道来!

笔者做了个简单的 demo。为了调试,把 ImageSlider 对应的 library 也下载到本地,让 demo 直接使用它。些 demo 的入口 Activity 为 ActMain(开启了 StrictMode)。在这个 ActMain 中有几个 Fragment,其中一个 Fragment 用上了 ImageSlider。在 APP 运行后,笔者反复触发 onConfigurationchanged。让 ActMain 反复创建销毁,触发十几次后。一看 Log,截图如下
这里写图片描述
从上图看到:ActMain 的实例有很多个,但是 StrictMode 告诉我们,ActMain 实例的数量上限是 1,这就是问题所在了。此外,在不断地触发 onConfigurationchanged 的过程中,笔者发现内存占用量在稳定增长,没有自动释放。
那么是什么原因导致 ActMain 的实例有那么多个呢?实例在 onConfigurationchanged 发生的时候,ActMain 会经历销毁然后重创建的过程。但是销毁后,ActMain 实例应该也会被销毁掉了才对,而这里显然没被销毁。说明在销毁的过程中,ActMain 实例被某些没被销毁的强引用链接着,Java 中的引用有强软弱虚种。在垃圾回收的时候,强引用是无法被垃圾回收掉的。那么是哪些强引用一直在引用着这些 ActMain 实例呢?仅从这里的 Log 是很难看得出来的。

于是笔记 Dump Java Heap,然后在 MAT 中打开这个 hprof 文件。如下图所示
这里写图片描述

接着打开 dominator_Tree,搜一下 ActMain,哇!好多 ActMain 实例 …
这里写图片描述

为了知道这些 ActMain 实例的强引用是谁,随便选择一个 ActMain 实例,右键选择 Path to GC Roots ——> 选择 exclude weak references。
这里写图片描述

看上图,显然是 Timer 引用着 ActMain(Activity 也是从 Context 继承下来的,这里的 mContext 实际上就是 ActMain)。而这些 Timer 是在 SliderLayout 中,于是笔者打开 SliderLayout.java 查看下源码,发现有两得地方启动了 Timer 执行 TimerTask,每隔几秒钟执行一次 TimerTask,无限地执行着。这样当然会导致上面的问题了,那么怎么办呢?我们知道其实在一个 View 对象被销毁的时候,它就应该释放它所拥有的资源,停止它的相关任务。笔者看了一下这个 SliderLayout,发现它没有覆盖父类的 onDetachedFromWindow() 方法,也没有什么机制能够让外部的类主动释放这个 SliderLayout 占有的资源。所以笔者直接给它添加重写 onDetachedFromWindow() 方法的代码,在这个方法 cancel Timer 和 TimerTask,取消 Handler 的所有 CallbacksAndMessages。代码片段如下

    @Override    public void onDetachedFromWindow() {        super.onDetachedFromWindow();        CustomLog.debug(LOG_TAG, "onDetachedFromWindow() ***");        if(mCycleTimer != null) mCycleTimer.cancel();        if(mCycleTask != null) mCycleTask.cancel();        if(mResumingTask != null) mResumingTask.cancel();        if(mResumingTimer != null) mResumingTimer.cancel();        mh.removeCallbacksAndMessages(null);    }

最后再次运行这个 demo,观察内存占用情况,Dump Java Heap 来分析,完全就没问题了。

0 0
原创粉丝点击