作为一个Android新手小白,刚到新公司,最近的工作就是在学习解各类Bug。转型之初,面临各种新知识,会有压力,但是学习的过程是快乐的。
上周刚遇上一类bug,就是应用的内存泄漏问题。最终通过前辈的指点,用了两天的时间(包括今天),来解决了这个问题,并最终发现了Android原生代码的bug(值得开心......)。因此将学习的过程总结出来,可以供像我一样的新人参考学习。
一. 问题发现的背景
QA测试发现,多次打开Android系统中设置功能里的某个Activity时,其占用的资源未能释放,并且在两三百次的重复操作后,设置应用发生了Crash的现象。
崩溃的原因是OOM问题,即占用的资源因未能被GC回收,导致内存不足,抛出了OOM(Out of Memory)的异常,应用发生Crash。
因此下一步就是RD来解决问题啦!
二. 需要准备/掌握的工具
没有工具的配合,你很难轻松的应对和解决问题。经过前辈的指导后,开始入手学习使用解此类问题的一系列工具。
1. Adb Shell 命令
Android新手入门一定先从Adb开始,Adb全称是Android debug bridge,提供很多操作手机的命令,有了它,可以方便的debug问题。这里我们使用的命令如下,
A. adb shell
进入adb shell,执行以下命令
B. am start -a "xxx" -d "xxx"
通过activity manager打开activity,方便多次测试,调查进程内存占用情况
C. dumpsys meminfo xxx
查看进程的内存占用情况,xxx为包名
2. DDMS + MAT工具
DDMS全称是Dalvik Debug Monitor Service,一般我用它来查看即时log,这里的作用是使用DDMS来生成hprof文件,hprof是Android进程的heap快照,有了它,可以来研究heap中存在哪些object,以及object的引用,研究为何GC没有回收对象的原因。
而MAT工具,正是由Eclipse提供的,能方便分析hprof文件的工具。MAT全称是Memory Analyzer Tool,内存分析工具,安装方式是在Eclipse中,选择install new software,然后提供插件的网址,选择安装即可。
因此这里我们的思路是,通过Adb shell命令来测试并重现问题,然后用DDMS来抓取heap快照,使用MAT来分析heap快照,从来对照代码解决问题。
三. 解决此内存泄漏问题的过程
1. 重现问题,通过am start命令直接打开此Activity,然后按手机的返回键,多次重复此过程。
2. 在步骤一的过程中,每次都使用dumpsys meminfo com.android.settings命令,来观察heap中Activity的数量。
正常的情况下,Activity的值应该为0或1,不应该持续增长,因为按返回后,如果不存在内存泄漏,无用的Activity对象会被GC(垃圾回收)给回收掉。
但这里,因为有问题存在,我观察到的现象是,Activity的数量一直在增长。如下图所示,heap中Activity的数量变化:
步骤一操作1次,
操作2次,
操作5次,
可以明显的看到问题的发生,即在我们每次操作过程中,Activity虽然已经通过返回键,不予显示,但是占用的资源未能被GC回收,每次操作都会生成一个新的不会且不会被释放的Activity对象,发生了内存泄漏!
因此下一步要来解决问题。
3. 使用DDMS+MAT发现线索,解决问题
既然现场已经重现,此时我们需要用DDMS来生成hprof文件,这里提到一点,如果你使用的都是Eclipse里安装的DDMS与MAT工具,在DDMS中点击生成hprof文件,会自动关联MAT,使用MAT打开此文件。
DDMS生成hprof文件,点击下图中的2个绿色按钮,如下,
MAT打开hprof文件,打开时建议选择第一项,如下,
之后打开后,就能分析heap文件啦。这里我们选择,点击Dominator Tree,它能列出heap中最大的对象们,
然后在打开的页面中,选择你测试时发现问题的Activity(可以使用关键词来过滤结果),这里出问题的Activity是,AppDrawOverlaySettingsActivity(Android原生代码),其对应的Fragment是DrawOverlayDetails。由于我们操作了5次,可以发现heap中的5个对象存在,都没有被释放。
这时要分析其未被释放的原因,要使用到MAT的功能来分析对象的引用,因为强引用的对象不会被GC回收。既然这个Activity对象一直存在,就说明一定是有引用存在,导致其未被GC回收。
我的做法是,右击object,点击Merge Shortest Paths to GC Roots -> exclude all phantom/week/soft etc. preferences(因为要排除弱引用,以及软引用,这些引用包含的对象都会被GC回收,对我们没有参考价值)。
之后便可以发现原因了,
通过查看其引用,发现存在一个可疑的mSession变量,它属于Activity的父类,在类中使用了当前的对象,但是一直未能释放,因此这就是问题的原因,导致GC未能回收资源。
知道原因后,解决的方法便很简单,就是在按返回键,触发的onStory或onDetach方法中,释放此mSession对象。最终便能解决问题。
@Override
public void onDestroy() {
super.onDestroy();
mSession.release();
}
最后提交修改,在新的apk测试中,通过Adb shell命令测试发现,Activity数量已维持正常,内存泄漏的问题便也已解决。
最后总结,解决内存泄漏的问题,熟练使用命令和工具很重要。有了它们的帮助,能快速的找到线索,再到代码中去发现问题。当然复杂的问题,远没有本文中解决的过程简单,但是对于新手来说,学习此方法步骤会有很大帮助!
- Kevin Song
2016年5月9日