Handler和内部类怎么引起内存泄漏?
先看下面一段代码:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
}
我们平时使用Handler的时候,相信很多人都会这样写一个Handler。但是这样会引起内存泄漏。Android Lint也会给出下面的警告:
In Android, Handler classes should be static or leaks might occur.
那么,这块代码的内存泄漏究竟怎么引起呢?先看以下几个知识点:
- 当一个Android应用第一次启动的时候,Framework会为整个应用程序的主线程创建一个
Looper
对象,一个Looper
对象维护了一个message queue
消息队列,并且Looper
对象会去一个一个的去轮询消息队列里的Message
消息对象。应用程序框架的所有主要事件(例如,Activity生命周期方法的回调,一个Button的点击事件等)都会被一个Message
对象持有,并添加到Looper
的message queue
中被一个接一个地去处理。主线程中的Looper
对象的生命周期贯穿整个应用的生命周期。 - 当一个
Handler
在主线程被创建,它就和Looper
以及Looper
所对应的MessageQueue
关联起来了,被这个Handler
对象send
到message queue
中的所有Message
对象都会持有这个Handler
对象的引用,正因为如此framework
才可以去调用相应Handler
的handleMessage(Message)
方法去处理相应的消息。这句比较拗口,Handler
对象send
一个Message
对象的时候,会将Handler
自身赋值给Message
的target
属性,处理该消息的时候会取出该target
并调用它的handleMessage(Message)
方法去处理,所以每个Message
对象都会持有send
它的那个Handler
对象的引用。 - 在Java中,非静态的内部类和匿名内部类都会隐式的持有它们外部类的引用;而静态内部类不会。
知道了以上几个知识点,就可以去检查哪里会出现内存泄漏了,再看下面的代码:
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
被finish
掉的时候,Handler
发送的这个Message
会继续在主线程的message queue
中存在1000*60*10ms
才会被处理掉。这个Message
持有了activity
中Handler
对象的引用,并且Handler
内部类又隐式地持有它的外部类SampleActivity
的引用,在Message
被处理之前,这个引用链会一直存在,从而会阻止activity
的context
被垃圾回收器回收,这样就会导致这个activity
引用的所有resources
造成内存泄漏。(Message
被处理掉之后,再遇到GC,该Message
对象就会被回收,其引用的Handler
对象也会被回收,相应的activity
也就可以被回收了,如果没有其他引用继续引用它时)。代码中的new Runnable
也是同理。非静态的匿名内部类也会隐式地持有外部类的引用,也会造成内存泄漏。
解决这种情况造成的内存泄漏
要解决这个问题,可以在单独的java
文件里去写一个类继承Handler
或者将Handler
内部类声明为static
,static
修饰的内部类不会持有外部类的引用,也不会造成activity
的泄漏。如果你要在内部类中调用activity
的方法,可以使用Activity
的WeakReference
。要解决匿名内部类Runnable
造成的内存泄漏,我们可以将该匿名内部类声明为activity
的静态属性(匿名内部类的静态实例不会持有外部类的引用)。
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
的onPause
、onStop
或onDestroy
中remove
掉相应的message
或callback
。
结论:
如果一个内部类的实例的生命周期比Activity的生命周期长,就避免使用非静态的内部类,改用静态内部类。
附:静态变量和静态内部类
static
关键字在静态变量和静态类中的作用不同。
静态变量意思是该变量属于该类的所有实例,当该类的一个实例被GC回收掉之后,它并不会被GC回收掉,它会一直存在于内存中,直到你显示地给它赋值为null。把一个Drawable对象设置成static
修饰的,因为一个Drawable
对象往往会持有一个View
的引用,而View
对象往往又会持有activity
的引用,所以一个static
的Drawable
对象会强制使activity
在被销毁后还要在内存中存在,除非你显示地将Drawable
置为空。
static
修饰的class和static
修饰的变量意义不同。static
修饰的内部类就像在单独的.java
文件中声明的类一样;一个非静态的内部类则和它的外部类有隐式的关联,一个非静态内部类的实例不能脱离了外部类的实例而单独存在。
总之记住一句话,在activity
内部如果要使用内部类,尽量使用static
,成员变量尽量少用static