你还在写导致 context 内存泄露的代码吗?

本文翻译自: http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

作为一个 Android 程序员,一定对下面这段代码很是熟悉:

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) {
// ...
}
}
}

你或许认为这段代码写很正常,没有任何问题,那你就大错特错了。这段代码真是导致 context 内存泄露的罪魁祸首,Android Lint 可能会给你以下警告:

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

你也许会疑惑,内存泄露是如何发生的?让我们来追寻问题的源头:

  • 1.当一个 Android App 初次启动时,framework 层会为这个 app 的主线程创建一个 Looper 对象。此对象的内部实现是一个队列,用来处理 Message 对象。所有的应用 framework 事件(比如 activity 的生命周期方法调用,按钮的点击事件等)都交给这个 Message,然后再放到消息队列用处理。主线程的 Looper 对象存活在整个 application 的生命周期。
  • 2.当你在主线程中实例化了一个 Handler,也就意味着它与 Looper 的消息队列关联了,被发送到消息队列的消息会持有一个 Handler 的引用,以便 Android framework 层可以在 Looper 最终处理这个消息的时候,调用 Handler#handleMessage(Message)
  • 3.在 Java 中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会。

看了上面3点,你可能还不是很清楚到底什么是内存泄露?让我们再来看看下面这段代码:

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() { /* ... */ }
}, 1000 * 60 * 10);

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

当这个 Activity 被 finished 后,延时发送的消息会继续在主线程的消息队列中存活 10 分钟,直到他们被处理。这个消息持有这个 Activity 的 Handler 引用,这个 Handler 有隐式地持有他的外部类(在这个例子中是 SampleActivity )。直到消息被处理前,这个引用都不会被释放。因此 Activity 不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第 15 行的匿名 Runnable 类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此 context 将被泄露。

为了解决这个问题,Handler 的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的 Activity 泄露。如果你需要在Handle内部调用外部 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, 1000 * 60 * 10);

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

作为一个 Android 开发人员都应该了解。开发中不能碰的雷区是什么?不在一个 Activity 中使用非静态内部类, 以防它的生命周期比 Activity 长。相反,尽量使用持有 Activity 弱引用的静态内部类。

文章有帮助到您?不妨打赏博主一碗拉面或一杯咖啡的小费吧 :-D!