一次内存泄漏问题的发现与解决过程

来源:互联网 发布:高丝 艾文莉 知乎 编辑:程序博客网 时间:2024/05/17 23:27

一次内存泄漏问题的发现与解决过程

新手第一次解决内存泄漏问题,把过程分享一下。大神飘过就好。。。

0x00 发现问题

这个内存泄漏的问题是在解决另一个bug的过程中发现的,在发起报销的过程中,当一个报销类别的表单中包含了图片元素时,如果添加了图片,然后重复编辑几次,app会变的非常卡顿,最初怀疑是图片加载时没有压缩直接加载了原图,于是开始关注android studio的内存监控。

添加八张图片,重复编辑了若干次以后,内存占用如图所示:

很显然,内存有一个明显升高的过程,怀疑产生了内存泄漏。

0x01 分析问题

由于审批模块涉及的类很多,一时间也无法找到究竟是在哪里发生了问题,于是想到了使用MAT(Memory Analyzer tool)来分析,那么首先要把发生泄漏的内存dump出来,打开ddms,开始重复刚刚产生内存泄漏的过程。
这一次我添加了4张图片到表单中,以下是每一次编辑审批单结束后,gc一次之后的数据:

填写完保存后:

第一次编辑:

第二次编辑:

第三次编辑:

我们主要关注Heap Size和Allocated的值,Heap Size是虚拟机分配的堆大小,这个值是会动态改变的,当堆的空间不够时,这个值会增大,当堆的值过大,堆的利用率降低时,这个值会变小,Allocated是当前堆空间中已经分配的大小,一次gc之后,资源被回收,这个值应该会变小,但是可以看到,每一次gc之后,Allocated的值都会增大25m左右,这说明有高达25m的内存没有被回收。那么具体是哪些资源没有被回收呢?需要借助mat工具来分析。

0x02 进一步分析

首先将内存dump出来,并使用sdk中的hropf-conv工具转换一下,用mat打开,在Leak Suspects视图中,可以看到当前内存的分配情况,如图所示:

mat的分析结果显示,有4个地方可能产生了内存泄漏,分别是:




前三个是那个自定义的可删除列表控件,最后一个是imageloader,最后那个我猜想应该是imageloader的内存缓存,估计是误报,那么前三个就应该是这次内存泄漏的元凶了。

DeleteableListView是用来展示图片的,如果这个控件没有在gc的时候销毁,那么它持有的图片资源也自然不会释放,众所周知,图片是非常占用内存的,但是,为什么DeleteableListView没有被销毁呢,当展示表单的activity被finish时,这个activity持有的视图资源应该会被gc掉的,如果没有被gc,那说明一定还有其他类持有了这个DeleteableListView的实例。

这个时候,就需要来仔细检查这个DeleteableListView实例的引用关系了,使用mat,在这个实例上右键然后Path to GC Roots,我们可以看到引用关系,如图所示

这个实例应该是被两个类持有,一个是BaseForm,另外一个间接被EventBus持有,被BaseForm持有很好理解,因为Baseform继承了Linearlayout,实际上就是DeleteableListView的父布局,但是为什么会间接被EventBus持有呢?仔细看一下引用关系不难发现,其实,这是因为DeleteableListView被ImageElement引用,ImageElement又被EventBus引用。这样,问题就很清楚了。

0x03 解决问题

在ImageElement中,我将这个类注册到了eventbus,用来处理一系列事件,比如删除,添加等,为了将逻辑和视图解耦,ImageElement并不是一个继承自View的类,而是将视图包装在了自身内部,通过bind()方法返回给外层的视图容器,这样一来,我需要将ImageElement的生命周期和view的生命周期绑定在一起,没错,我确实这么做了,而且在imageelement的onDetachedFromWindow方法中我也将这个类从eventbus中移除了,但是,在Activity的onDestory方法中,我忘记调用BaseForm的onDestory方法来触发每一个元素的onDetachedFromWindow方法。

好吧,真想给自己一巴掌。。

补充上了onDestory后,再次尝试一下添加图片后多次重新编辑,内存占用如下:

问题被解决了!

0x04 总结

总结了一下这次问题产生的原因,最重要的有两点,第一是审批部分的代码写的太过复杂,有过度设计的嫌疑,导致了根本无法直观的发现问题所在,另一点,就是对eventbus的理解不够准确,以前在activity中直接使用时,只是按照习惯在oncreate和ondestory中调用一下注册和解绑的方法,没有理解这样做的原因。看来以后使用第三方库时,还是应该多看文档,有空还是需要继续RTFSC。

0 0
原创粉丝点击