Android Performance: Memory Monitor & Heap Viewer

Memory Monitor可以监视应用的内存使用,并以可视化的方式显示出来。Heap Viewer可以实时地报告应用在内存中分配的对象的类型、数量和在堆上占用的空间。二者都是分析应用内存分配和管理的工具,能够帮助定位内存泄漏等问题。

  下面例子使用的代码可以在这里找到,运行后点击“MEMORY LEAKS”,如图1所示。

图1

图1

1. Memory Monitor

  在Android Studio中打开Android Monitor面板,选择Monitors选项卡,可以看到在Memory一栏记录了当前应用的内存使用情况。深蓝色背景的区域为应用占用的内存,浅蓝色背景的区域为系统分配给应用的可用内存。反复旋转设备,使屏幕发生旋转,可以看到随着屏幕反复旋转,应用占用的内存不断增加,内存占用量增加到一定程度之后,发生垃圾回收,内存占用突然下降,如图2所示。

图2

图2

  继续旋转屏幕,可以观测到后续的内存回收,如图3和图4。

图3

图3

图4

图4

从图3和图4可见,尽管经过了垃圾回收,但应用消耗的内存不断增加。在内存回收时,淡蓝色的区域也提高了,说明系统为应用分配了更多的可用内存。

2. Heap Viewer

  打开Android Device Monitor (Tools -> Android -> Android Device Monitor),如图5所示,在左侧Devices下选择要查看的应用com.example.android.mobileperf.compute(1),选中工具栏的Update HeapUpdate Heap(2),点击右上的DDMS页面(3),再点击右侧边栏的HeapHeap(4),打开Heap Viewer界面。反复旋转屏幕,当垃圾回收发生后,Heap的状态会实时更新(5),可见当前分配了2.873MB内存,垃圾回收释放了1.916MB内存。

图5

图5

  继续反复旋转屏幕,Heap Viewer会在垃圾回收发生时更新,如图6、图7、图8所示。

图6

图6

图7

图7

图8

图8

可见应用占用的内存在不断增加。

  从Memory Monitor和Heap Viewer都可以看到,随着不断地屏幕旋转,虽然垃圾回收会正常进行,但应用占用的内存会不断增加,很可能存在内存泄漏问题。屏幕旋转会销毁并重新创建Activity,一个很容易发生的问题是在Activity销毁时还保留了某些引用,导致有对象残留了下来,造成内存泄漏。

  在MyCustomView.java可以找到如下一段代码:

private void init() {
    ListenerCollector collector = new ListenerCollector();
    collector.setListener(this, mListener);
}

其中ListenerCollector定义如下:

public class ListenerCollector {
    // A common case is to want to store all the listeners for a particular type of view
    // somewhere.  Harmless AND convenient.  Or... is it? o_0
    static private WeakHashMap<View, MyListener> sListeners = new WeakHashMap<View,  MyListener>();

    public void setListener(View view, MyListener listener) {
        sListeners.put(view, listener);
    }
}

这里相当于把每个mListener都保存了起来,当屏幕旋转,Activity被销毁时,mListener并没有被释放,导致内存泄漏。应当在Activity结束时释放掉相关的Listener,如:

@Override
protected void onStop() {
    super.onStop();
    // By clearing out the set of listeners when the Activity stops, we can avoid excessive
    // memory leaks. When the Activity is re-started, the custom view will be created and
    // will then create its own listener again.
    ListenerCollector.clearListeners();
}