0%

Android 中 Handler 导致的内存泄漏

问题介绍

在Android开发过程中,我们经常用Handler来处理异步操作的返回结果,却没注意到很容易发生内存泄漏,代码通常如下:

1
2
3
4
5
6
7
8
public class MainActivity extends Activity {
private Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// do something
}
};
}

上面代码很常见,但是却是能引起内存泄露的,我们用Android Lint工具检查一下代码,会发现如下警告:

1
2
3
Handler reference leaks
MainActivity.java
This Handler class should be static or leaks might occur

然后具体解释如下:

1
2
3
4
5
6
7
8
9
Since this Handler is declared as an inner class, it may prevent the outer class from 
being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread,
then there is no issue.
If the Handler is using the Looper or MessageQueue of the main thread,
you need to fix your Handler declaration, as follows:
Declare the Handler as a static class; In the outer class, instantiate a WeakReference
to the outer class and pass this object to your Handler when you instantiate the Handler;
Make all references to members of the outer class using the WeakReference object.

问题分析

1. Android 方面

当Android应用启动的时候,系统会自动创建一个供主线程使用的Looper实例。

1
2
3
4
5
6
7
8
/// file: frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
...
// 主线程中调用Looper.prepareMainLooper()方法创建Looper
Looper.prepareMainLooper();
...
}

Looper的主要工作就是一个一个处理消息队列(MessageQueue, Looper构造方法中创建)中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。

我们在主线程中实例化一个Handler时,如果没有指定其它的Looper,那么它就会自动使用主线程的Looper,如下图所示log:

所以我们发送一条消息到此Handler时,实际上消息是进入了主线程的消息处理队列,而此消息已经包含了一个Handler实例的引用:

1
2
3
4
5
6
7
8
9
10
/// file: frameworks/base/core/java/android/os/Handler.java

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 消息获得当前Handler的引用
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

当Looper来处理消息时,会据此引用来回调[Handler#handleMessage(Message)]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void loop() {
final Looper me = myLooper();

final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// 取到消息之后,交给发送该消息的Handler来处理消息
msg.target.dispatchMessage(msg);
} finally {
...
}
...
msg.recycleUnchecked();
}
}

2. Java方面

Java中的非静态内部类以及匿名内部类会持有外部类的引用。静态内部类不会持有外部类的引用。

3. 内存泄漏所在

结合Android和Java方面的分析,我们应该很容易就知道了为什么会产生内存泄漏:

  1. 发消息到Handler,消息进入到主线程的消息队列后持有对Handler的引用
  2. 做为非静态内部类,Handler又持有外部类(在这里是MainActivity)的引用
  3. 只要消息没被处理,那么MainActivity对象就无法被垃圾回收器回收,进而导致MainActivity持有的很多资源都无法回收,这就是我们常说的内存泄漏。

泄漏解决方案

  1. 避免使用非静态内部类,前面有提到过静态内部类不会持有外部类的引用;
  2. 将Handler放在单独的文件中,而不是以内部类方式存在;
  3. 当需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MainActivity extends AppCompatActivity {
    // 静态内部类,不会持有外部类的引用
    private static class MyHandler extends Handler {
    // 外部类的弱引用
    private WeakReference<MainActivity> mActivity;

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

    @Override
    public void handleMessage(Message msg) {
    MainActivity activity = mActivity.get();
    // 确保外部类没有被回收
    if (activity != null) {
    // Do something ...
    }
    }
    }
    }

小结

其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。

课后思考

如下代码是否存在内存泄漏?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
}

private final MyHandler myHandler = new MyHandler();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myHandler.postDelayed(new Runnable() {
@Override
public void run() {

}
}, 1000 * 60 * 10);
finish();
}
}