作者:念杰
用户使用移动产品时,App 崩溃现象会严重影响用户体验。而Android 低端机器正是崩溃的重灾区,经常会抛出OOM(out of memory)异常。究其原因多数是App 使用过程中发生内存泄漏,因此为了提高App 的稳定性,测试过程中需要反复操作尝试定位此类问题。本文将针对Android 设备崩溃产生的常见场景和定位工具进行阐述。
一、Android 内存泄漏的常见场景
Android 程序多数基于Java 语言开发。Android 系统在运行时,会给每个应用分配一个Dalvik 虚拟机作为进程运行的基本单位。与Java 中的JVM 虚拟机类似,Dalvik 也实现了虚拟机中的垃圾回收功能,以保证资源的合理使用。内存泄漏行为就是指App 使用过程中没有按正常逻辑被GC 回收的对象。因此Java 程序常见的内存泄漏场景在Android 中同样适用,本文主要描述Android 特有的内存泄漏现象的场景,从而方便测试重现此类问题。
1. 页面的销毁与建立
场景:多次关闭、打开同一页面后,触发GC,内存增长,可能存在崩溃。
原因:Android 应用页面之间频繁的切换和绘制,可能会存在某些页面对象无法被GC回收,大量的相关对象停滞在Heap 区,从而导致内存泄漏。 举例来说为了加快页面渲染速度,经常会使用静态变量缓存页面背景。但是如果没有正确处理,在SDK-10 以下的系统上,会导致页面背景对象持有控件对象的引用,使整个Activity 都无法被GC 回收,多次重开同一个页面后,内存就会飙升了,而SDK-15 以上已经通过WeakReference 解决了这一问题。
2. 资源对象
场景:
a. Android 中常使用广播broadcast 来监听系统事件,如果注册对象后,未关闭注册,假如监听事件是很频繁的操作(如锁屏等),容易造成OOM。
b. 数据库游标,文件IO 流等没有及时关闭。这种场景需要在大量操作后,才会有明显的内存异常现象,很容易被忽略。
原因:资源对象未及时回收或关闭,从而导致内存泄漏
3. 图片
场景:大量图片的加载和切换
原因:图片资源相对于其他资源都较大,虽然系统能够确认Bitmap 分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过堆的限制,直接导致OOM,而崩溃产生的原因有以下2 点:
a. 使用完成后,没有及时的销毁。
b. 总是保留原图大小的对象。如果图片实际的显示区域较小,可以设置合适的采用率保存Bitmap 对象,减少大图的内存占用。
4. 横竖屏切换
场景:横竖屏切换
原因:横竖屏切换时,会对页面元素进行重绘,如果处理不当,Handler、Thread 等的数量会随着页面的重建次数增加而增加,内存泄漏问题就很容易被放大。
5. 列表的滑动与重载
场景:App 中很多内容都会通过列表的形式来展示,包括图片的浏览等,列表滑动和重载的过程可能会导致内存泄漏,列表中包含大量图片时,不必要的内存开支会导致内存资源不足。
原因:列表在滑动时,不可见的item 对象会被回收,而被用来构造新的item,若在构造每一个item 时,没有使用缓存的convertView,会造成内存垃圾。当快速滑动时,容易给垃圾回收较大压力,如果GC 来不及清理资源,虚拟机不得不分配更多内存来使程序正常运行。
二、Android 内存泄漏分析方法
既然了解了Android 内存泄漏可能出现的场景,那么接下来介绍具体的工具来辅助我们进行内存泄漏分析。Android tools 中自带的DDMS 就是一个很实用的内存检测工具,可以动态查看进程的heap 信息,跟踪内存分配情况以及线程状态,通过与内存分析工具MAT 的结合,可以方便的监控分析潜在内存问题。
1. DDMS 跟踪进程的heap信息
DDMS 工具可以在eclipse adt 插件或SDK tools 中打开。通过DDMS 的Devices 面板,可以查看设备运行中的进程,选中进程后,点击工具栏的Update Heap,即可开始该进程的heap 信息监控,如图1。
(Tips:如果使用安卓真机,那么只能看到本地Eclipse 开启Debug 开关编译到真机上的App 进程,要想看到所有的进程,那么刷成开发机吧。)
上文场景中,提到了Bitmap 使用不当时,容易引发内存泄漏,这里就以某产品的漫画功能作为实例。漫画页面占用内存的主要对象即为Bitmap !在DDMS 的Heap 面板里,通过Cause GC 可以触发GC,实时查看到当前的堆情况。图2 展示了一次GC 后的Heap 情况,系统分配的堆控件Heap Size 和应用程序实际占用的内存Allocated 已经增大到异常数量级,其中byte array 占用了大部分的空间。如果观察动态数据,可以看到在漫画翻页的过程中,每次GC 后,堆的大小一直增加,没有明显的回落,因此有内存泄漏的可能性。
当然图2 中Heap 的泄漏可能性比较明显,测试中也可以尝试通过下面两种方法来起到放大泄漏问题的效果:
1. 在不同App 间多次切换。可以通过Android 的Home 键,或者历史进程键来切换App,使App 在不同的生命周期内不断变化,通过不断的唤醒onResume 和onPause 暂停页面时,页面对象处理不当引发的泄漏问题,会进一步暴露出来。
2. 多次切换横竖屏。横竖屏切换经常可以引发App 中Activity、Context、View 等对象的泄漏,因为横竖屏切换会使页面重新加载。如果App 在其他代码中保持了对上述对象中某一个的引用,系统GC 将无法回收重载过程中本应该回收的资源,Heap 会有明显的增加。
既然监控发现了可能的内存泄漏问题,可以借助hprof 文件来分析泄漏的根源所在。回到Devices 面板,点击Dump HPROF file,即可导出当前GC 后Heap 的详细信息。当然并不是所有内存泄漏都能由OOM 或者Heap Size 的变化来检测,需要进一步分析Heap 的具体使用情况才能明确问题所在。
2. MAT 分析内存泄漏原因
HPROF 文件可以使用Java 常用的内存分析工具MAT 来分析,其中Dominator Tree 和Histogram 应该是最有用的工具。在图3 的dominator_tree 列表中,com.netease.x.x.x 直接或间接引用到的Retained Heap 相当大,再逐层查看,发现存在30 多个Bitmap 对象没有被GC 回收。
通过Histogram 也可以看到byte[] 占用了大部分的Shallow Heap(这是由于Android3.1之后,Bitmap 像素数据的内存分配在Dalvik Heap 中), 如图4。选择Path To GC Roots->exclude weak references,可以看到对象的逐层引用关系,找到内存泄漏的根源。
在移动产品Android 端的测试过程中对Heap 和GC 信息的监控是必要的,通过这些宏观的监测数据,发现疑现象,深入分析找到内存泄漏的根源,对于解决崩溃现象,提高测试产品的稳定性,有很大的帮助。
网易云产品免费体验馆,无套路试用,零成本体验云计算价值。
本文来自网易实践者社区,经作者念杰授权发布