Handler使用引起内存泄漏分析
首先我们来看下面一段代码:
public class MainActivity extends Activity {
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
// just finish this activity
finish();
}
当我们在Activity类中定义一个Handler的内部类时,ADT工具会在旁边有警告显示
This Handler class should be static or leaks might occur
这表明这样的代码会造成严重的内存泄漏,那么Handler是如何造成内存泄漏的呢?
我们知道当应用程序启动时,UI线程是从Activity Thread运行的,在该类中的main()方法中,已经使用Looper.prepareMainLooper()为该线程添加了Looper对象,这个Looper对象包含一个消息队列MessageQueue,并且能够循环的处理队列中的消息。主线程即UI线程的Looper对象会伴随该应用程序的整个生命周期。
当我们在 UI线程中实例化这个Handler对象时,他就会自动的与线程中Looper的消息对象关联起来,所有发送到消息队列的消息Message都会拥有对handler的引用。而在java中,内部类和匿名类都会保持对外部类的引用。
当finish activity时,里边的延时消息(假如有的话)在得到处理前会一直保存在消息队列中,而且这些message保持对handler的引用,而handler又保持对外部类即activity的引用,这阻止了activity被垃圾回收器回收,从而造成了内存泄漏。
解决此问题正如ADT的提示所说可以讲handler变成静态内部类或者放在另外的文件中,静态内部类不会持有对外部类引用,另外如果我们想要在handler内部去调用外部类Activity,我们可以在handler内部使用弱引用的方式指向所在Activity,这样也不会导致内存泄漏,代码如下所示:
private MyHandler myHandler = new MyHandler(this);
private static class MyHandler extends Handler
{
private final WeakReference<MainActivity> wActivity;
public MyHandler(MainActivity activity) {
super();
this.wActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
MainActivity mainActivity = wActivity.get();
if(mainActivity!=null)
{
//....
}
}
}
但是我们再思考一下,当Activity销毁的时候,那些延时的消息还在队列着,这个情况下这些消息还有没有必要进行处理呢?如果没有必要的话,那需要如何做呢?
查看Handler的API,有几个方法:removeCallbacks(Runnable r)和removeMessages(int what),我们可以在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。代码如下:
/**
* 一切都是为了不要让mHandler拖泥带水
*/
@Override
public void onDestroy() {
myHandler.removeMessages(MESSAGE_1);
myHandler.removeMessages(MESSAGE_2);
myHandler.removeMessages(MESSAGE_3);
myHandler.removeCallbacks(mRunnable);
myHandler.removeCallbacksAndMessages(null);
// ... ...
}
经验总结:
1. 注重基础,只有在基础扎实了才能写出健壮的代码
2. 对于adt给出的警告,一定要重视,虽然这些警告并不影响程序的运行,但是这些警告可能会导致程序的崩溃