如何防止内存泄漏

#Memory Leak

考虑下面的代码块:

1
2
3
4
5
6
7
8
9
public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

也许不是那么明显,这块代码能造成大量的内存溢出。Android Lint 会提示如下警告

In Android, Handler classes should be static or leaks might occur.

但是内存是在哪儿溢出的,是如何发生的?首先通过列出我们已经知道的事实来找问题的根源:

1.当Android应用程序第一次启动时,framework在应用程序的主线程里创建了一个Looper对象。Looper实现了一个简单的消息队列,依次处理队列里的消息对象。所有的应用程序框架里的事件(比如Activity的生命周期函数调用,按钮点击事件等等)都封装为消息对象,被加入到Looper的消息队列中并依次处理。主线程的Looper在整个应用的生命周期里会一直存在。

2.当在主线程里实例化一个Handler时,就和Looper的消息队列关联在一起了。发送到消息队列的消息会持有Handler的引用,使得当Looper最后处理消息时候,framework可以调用Handler#handleMessage(Message).

3.在Java中,非静态匿名内部类会持有它所在外部类的隐式引用。而静态内部类不会。

那么到底内存是在什么地方泄漏的?非常巧妙,思考下面一段示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { }
}, 60 * 10 * 1000);

// Go back to the previous Activity.
finish();
}
}

当activity结束掉,延迟的消息会在主线程的队列中生存10分钟,直到被处理。该消息持有activity Handler的引用,且Handler持有外部类的隐式引用(当前情况下是SampleActivity).该引用会持续存在直到消息被处理,因此妨碍了Activity Context被垃圾回收,从而导致应用程序资源泄漏。注意在15行的匿名内部类会导致一样的情况。非静态匿名内部类的实例持有它们外部类的的隐式引用,因此Context会泄漏。

为了修复这个问题,创建一个类继承Handler或者使用静态内部类。静态内部类不会持有它的外部类的隐式引用,所以activity不会溢出。如果需要在Handler中调用外部activity的方法,使Handler持有activity的WeakReference,因此不会导致Context意外溢出。当实例化匿名的Runnable类时,为了修复内存溢出,为这个类创建一个静态域(因为匿名内部类的静态实例不会持有它的外部类的引用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class SampleActivity extends Activity {

/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/

private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;

public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}

private final MyHandler mHandler = new MyHandler(this);

/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/

private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

// Go back to the previous Activity.
finish();
}
}

静态和非静态内部类之间的区别很微妙,但每个Android程序员都应该理解它。底线是什么?如果内部类的实例比activity的
生命周期长,避免在activity中使用非静态内部类。相反,推荐使用静态内部类,持有activity的弱引用。

以下是原文链接:
How to Leak a Context: Handlers & Inner Classes