分析:
在Android系统中,Handler是一个消息发送和处理机制的核心组件之一,与之配套的其他主要组件还有Looper和Message,MessageQueue。
Message和Runnable类是消息的载体。MessageQueue是消息等待的队列。Looper则负责从队列中取消息。
Handler有两个主要作用:
1.安排调度(scheule)消息和可执行的runnable,可以立即执行,也可以安排在某个将来的时间点执行。
2.让某一个行为(action)在其他线程中执行。
Handler是由系统所提供的一种异步消息处理的常用方式,一般情况下不会发生内存泄露。
Handler为什么可能造成内存泄漏。这里的内存泄漏,常常指的是泄漏了Activity等组件。
public class ShanActivity extends Activity{
public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
这有什么问题呢。问题在于该Handler的实例采用了内部类的写法,它是ShanActivity这个实例的内部类,在Java中,关于内部类有一个特点:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。所以,该handler实例持有了ShanActivity的一个引用。
生命周期较长的组件引用了生命周期较短的组件。Handler就是一种典型的示例,以上面的代码举例。ShanActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。
问题原因:
一般非静态内部类持有外部类的引用的情况下,造成外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。这里 Handler 持有外部类 Activity 的引用,而handler有又未处理完的message,一旦 Activity 被销毁,而此时 Handler 依然持有 Activity 引用,就会造成内存泄漏。
解决方法:
1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。这个场景是及其常见的,因为handler经常被用来发延时消息。一个补救的办法就是在该类需要回收的时候,手动地把消息队列中的消息清空:mHandler.removeCallbacksAndMessages(null);
2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)
3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)
4.将Handler放到抽取出来放入一个单独的顶层类文件中。
这里需要了解一下关于Java里面引用的知识:
强引用(Strong Reference) | 默认引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。在内存空 间不足时,Java虚拟机宁愿抛出OutOfMemory的错误,使程序异常终止,也不会强引用的对象来解决内存不足问题。 |
---|---|
软引用(SoftReference) | 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。 |
弱引用(WeakReference | 在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 |
虚引用(PhantomReference) | 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 |
第三种,需要一些额外的代码,比较通用。
public class ShanActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<ShanActivity> mActivity;
public MyHandler(ShanActivity activity) {
mActivity = new WeakReference<ShanActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
ShanActivity activity = mActivity.get();
if (activity != null) {
//do Something
}
}
}
第四种方式,抽取做单独封装。
/**
* 实现回调弱引用的Handler
* 防止由于内部持有导致的内存泄露
* 传入的Callback不能使用匿名实现的变量,必须与使用这个Handle的对象的生命周期一
* 致否则会被立即释放掉了
*/
public class WeakRefHandler extends Handler {
private WeakReference<Callback> mWeakReference;
public WeakRefHandler(Callback callback) {
mWeakReference = new WeakReference<Handler.Callback>(callback);
}
public WeakRefHandler(Callback callback, Looper looper) {
super(looper);
mWeakReference = new WeakReference<Handler.Callback>(callback);
}
@Override
public void handleMessage(Message msg) {
if (mWeakReference != null && mWeakReference.get() != null) {
Callback callback = mWeakReference.get();
callback.handleMessage(msg);
}
}
}
由于是弱引用,当该类需要被回收时,可以直接被回收掉。
WeakRefHandler的使用时如下:
private Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch(msg.what){
}
return true;
}
};
private Handler mHandler = new WeakRefHandler(mCallback);
总结:
handler改为弱引用不是一概而论(大家只考虑在activity问题,handler持有activity的引用),解决问题可以传activity弱引用给handler就行,如果在service后台用到handler,难道也弱引用?合理使用handler,要明白为什么泄漏,不是所有场景都能用弱引用
tip:
removeCallbacksAndMessages: remove所有message和runnable
removeCallbacks: 只remove message,无法remove runnable