Contents

最近开始优化以前程序中的代码,目标是消除掉所有的警告。然后今天就碰到了使用Handler时的一条警告信息:

This Handler class should be static or leaks might occur
下面我们来详细分析一下这条警告的由来及相应的解决办法。

通常我们习惯于在UI主线程中直接这样使用一个Handler类:

1
2
3
4
5
6
7
8
public class MyActivity extends Activity {
private final Handler my_Handler_ = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO you actions
}
}
}

其实,如果把问题扩散开来,是一个更具普遍意义的问题:inner class 要不要设置成static的?
先上答案:一定要!

这个问题,Romain Guy在google自家的android开发者论坛上很早就有回答,我也是在搜索Stackoverflow的时候偶然看到的:

Romain Guy's suggestion

这他建议的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OuterClass {
class InnerClass {
private final WeakReference<OuterClass> mTarget;

InnerClass(OuterClass target) {
mTarget = new WeakReference<OuterClass>(target);
}

void doSomething() {
OuterClass target = mTarget.get();
if (target != null) target.do();
}
}
}

引用某位不知名的大牛的blog中很清晰的解释:

  1. Android App启动的时候,Android Framework 为主线程创建一个Looper对象,这个Looper对象将贯穿这个App的整个生命周期,它实现了一个消息队列(Message Queue),并且开启一个循环来处理Message对象。而Framework的主要事件都包含着内部Message对象,当这些事件被触发的时候,Message对象会被加到消息队列中执行。
  2. 当一个Handler被实例化时(如上面那样),它将和主线程Looper对象的消息队列相关联,被推到消息队列中的Message对象将持有一个Handler的引用以便于当Looper处理到这个Message的时候,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
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被finish()掉,Message 将存在于消息队列中长达10分钟的时间才会被执行到。这个Message持有一个对Handler的引用,Handler也会持有一个对于外部类(SampleActivity)的隐式引用,这些引用在Message被执行前将一直保持,这样会保证Activity的上下文不被垃圾回收机制回收,同时也会泄露应用程序的资源(views and resources)。

为解决这个问题,下面这段代码中的Handler则是一个静态匿名内部类。静态匿名内部类不会持有一个对外部类的隐式引用,因此Activity将不会被泄露。如果你需要在Handler中调用外部Activity的方法,就让Handler持有一个对Activity的WeakReference,这样就不会泄露Activity的上下文了。

我还在Stackoverflow的回答上看到一条回答是这样的:

Stackoverflow

意思是没有必要使用WeakReference, 点赞的人同样很多,我后来才明白,他们讨论的是说non-static的inner class会有强引用,而Romain的建议里面似乎是忘记把InnerClass声明为static的了。所以,Tomasz才会说,WeakReference没有什么用,因为如果你的Inner Class不是static的,那自动有一个outer class的强引用了~
但是我们可以看到,老外在这方面的严谨程度,Sogger在回答里面也提到了这一点,但是他后面补充了一句,有可能Romain使用了一种酷炫的编译器如果inner class没有使用outer class的成员变量,那么就自动把inner class设置成static的??Intresting~

结论:
无论如何,如果你要使用嵌套的class,最好要把它设置成static的。
否则,inner class将会持有outer class的一个强引用,这样在多线程操作中,非常容易出现内存泄漏。

最后附上我的部分源代码:

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
private static class MyHandler extends Handler {
/**
* weak reference member variable
*/
private final WeakReference<SplashActivity> splash_activity_;

/**
* Description: Copy constructor
* Created by Michael Lee on 6/23/16 17:14
* @param activity outer activity instance
*/
public MyHandler(SplashActivity activity) {
splash_activity_ = new WeakReference<>(activity);
}

/**
* Description: override method from {@link Handler#handleMessage(Message)}
* Created by Michael Lee on 6/23/16 17:14
* @param msg received message from other thread
*/
@Override
public void handleMessage(Message msg) {
SplashActivity splash_activity = splash_activity_.get();
if (splash_activity != null) {
// Do You Actions
}
}
}
Contents