在使用handler 的时候经常会出现这样一条警告: This Handler class should be static or leaks might occur 。
这条warning不会影响代码的编译和执行,但既然弹出了一条warning,肯定是有问题的,希望能把这个点给解开。
从stackoverflow上找到的解答是这样的(【4】):
If MyHandler class is not static, it will have a reference to your Service/Activity object.
Handler
objects for the same thread all share a common Looper object, which they post messages to and read from.
As messages contain target Handler
, as long as there are messages with target handler in the message queue, the handler cannot be garbage collected. If handler is not static, your Service/Activity cannot be garbage collected, even after being destroyed.
这条解答非常明白,下面我将把解答里的话逐条解释一下。可以找到handler警告的原因和解决方案。
1. If MyHandler class is not static, it will have a reference to your Service/Activity object.
涉及到了java中内部类与静态内部类的差别。这一部分的知识,我参考了文献【1】【2】。
内部类有一个指向外部类的引用,而静态内部类没有指向外部类的引用。(这一点也导致了内部类和静态内部类使用范围上的差异!)更本质的原因是因为他们存放的方式不同。
静态内部类的特点有:
不可以使用外部类的非静态成员或者其他内部类成员。(原因是:静态内部类没有指向外部类的引用)
如果创建静态内部类的对象,不需要其外部类的对象。(直接用外部类的类名即可) 一个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员。一些背景知识:java的内存管理。通过了解java的内存管理才能分清静态成员和非静态成员的本质差异。
(栈内存,堆内存,方法区)
堆:堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
栈:栈中存放一些基本类型的变量和对象句柄(其实也是int型)。
堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址。栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
方法区:在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来
2. Handler
objects for the same thread all share a common Looper object, which they post messages to and read from.
这句话介绍了hanlder的使用背景,意思是,同一个线程中的handler类型的对象,在处理message消息时共享一个looper。关于looper和handler的关系可以参考我的上一篇博客
中的第三部分。
3. As messages contain target Handler
, as long as there are messages with target handler in the message queue, the handler cannot be garbage collected. If handler is not static, your Service/Activity cannot be garbage collected, even after being destroyed.
这句话想表达的意思,应该是:
handler类型的对象,其生命周期是不可控的。
如果用了myHandler.dispatchMessage(msg)函数,除非msg在looper中被传递给myHandler执行,否则handler一直存在。
这样可能带来的一个后果是,msg还在looper中排队,而此时myhandler所对应的外部类应该被destroy回收内存,但由于handler是其内部类,占用了外部类的引用,使得外部类不能被回收,从而造成了内存泄漏。
这样再读stackoverflow的解答方案,就可以归结成:
内部类有一个指向外部类的引用,而静态内部类没有指向外部类的引用。handler类型的对象,其生命周期是不可控的。handler如果是普通内部类,由于其隐式的有一个指向外部类的引用,导致外部类在destroy后不能被GC回收,导致内存泄漏。
用一句通俗的话讲,这个warning提醒的目的,是把handler和外部类解耦合,避免内存泄漏。
问题被梳理清楚了,解决方案自然也通透,就是把handler声明为静态内部类。
新的问题又出现了,静态内部类不可以使用外部类的非静态成员或者其他内部类成员!在一些特定的应用中,handler需要使用到外部类的非静态成员,怎么办呢?
也有成熟的解决方案,使用WeakReference。WeakReference是弱引用,其中保存的对象实例可以被GC回收掉。这个类通常用于在某处保存对象引用,而又不干扰该对象被GC回收。参见【3】
使用方法如下:
1 private Handler mHandler = new MyHandler(this); 2 private static class MyHandler extendsHandler{ 3 private final WeakReferencemActivity; 4 public MyHandler(Activity activity) { 5 mActivity = newWeakReference (activity); 6 } 7 @Override 8 public void handleMessage(Message msg) { 9 System.out.println(msg);10 if(mActivity.get() == null) {11 return;12 }13 }14 }
参考文献:
【1】
【2】
【3】
【4】