本文翻译自: http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
作为一个 Android 程序员,一定对下面这段代码很是熟悉:
1 | public class SampleActivity extends Activity { |
你或许认为这段代码写很正常,没有任何问题,那你就大错特错了。这段代码真是导致 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 | public class SampleActivity extends Activity { |
当这个 Activity 被 finished 后,延时发送的消息会继续在主线程的消息队列中存活 10 分钟,直到他们被处理。这个消息持有这个 Activity 的 Handler 引用,这个 Handler 有隐式地持有他的外部类(在这个例子中是 SampleActivity )。直到消息被处理前,这个引用都不会被释放。因此 Activity 不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第 15 行的匿名 Runnable 类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此 context 将被泄露。
为了解决这个问题,Handler 的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的 Activity 泄露。如果你需要在Handle内部调用外部 Activity 的方法,那么让 Handler 持有一个 Activity 的弱引用(WeakReference)以便你不会意外导致 context 泄露。为了解决我们实例化匿名 Runnable 类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。
1 | public class SampleActivity extends Activity { |
作为一个 Android 开发人员都应该了解。开发中不能碰的雷区是什么?不在一个 Activity 中使用非静态内部类, 以防它的生命周期比 Activity 长。相反,尽量使用持有 Activity 弱引用的静态内部类。