Handler的作用
Handler消息机制在Android中的应用非常广泛,很多组件的底层实现都是靠Handler来完成的,所以掌握Handler消息机制的原理还是非常重要的。Handler的主要功能有两点:
1.它可以在不同的线程之间传递消息
我们都知道Andorid中规定只有主线程里可以进行UI操作,在其他线程中绝不可以进行与UI相关的操作,否则会报错。还有就是在主线程中也不可以进行耗时操作,否则会阻塞主线程,报ANR。基于以上这两点,我们就可以使用Handler在子线程中处理耗时操作,然后把返回的结果传给主线程,再进行UI方面的更新处理等。
2.它可以延时传递消息。
如果我们想要处理一个延时操作,一般可以通过Thread.sleep(times)使线程休眠几秒,然后再处理数据。但是再Android中是绝不能允许在主线程红进行休眠操作阻塞线程的,所以我们可以通过Handler来发送延迟消息的方式实现延时操作。
消息处理机制
Android的消息处理机制表面上只是使用了Handler,但其实还用到了其他的东西,比如说ThreadLocal,Looper,MessageQuery等,这些组合在一起使用,才构成了整个Android消息机制。
我们先回顾一下Handler的使用:我们以 “在子线程处理完耗时操作,然后通过Handler发送消息到主线程更新UI”为例。首先要先在主线程创建一个Handler对象,重写它的handlerMessage()方法,处理更新UI的操作。然后我们在子线程进行处理耗时操作,当执行完成后,我们创建一个Message对象,将需要传递的数据存入Message对象中,再调用Handler对象的sendMessage()方法将该Message对象发送出去。之后我们就能在Handler的handlerMessage()方法中接收到该Message对象了,然后取出其中的数据,进行更新UI的操作即可,这样整个过程就算完成了。
上面说的例子是从子线程发送消息回主线程,然后再主线程中处理消息。如果是通过主线程发送消息到子线程,然后在子线程处理消息,那又应该如何做呢?我们尝试直接在子线程中创建Handler对象,重写handlerMessage()方法,然后在主线程中发送消息,但程序运行后会报如下错误:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
意思是“不能够在一个没有执行过Looper.prepare()方法的线程中去创建Handler对象”,到这里我们终于能够看到Looper的影子了。那么这里就有一个问题,为什么在主线程中没有要求我们先调用Looper.prepare()方法呢,或者说与普通线程相比,主线程有什么特别的吗?
其实这是因为在主线程中,系统已经调用过该方法了,所以不需要我们再去手动调用。代码如下,主线程在刚开始创建的时候它的Looper就已经调用了prepareMianLooper()方法,所以我们能直接创建Handler。
public final class ActivityThread { public static void main(String[] args) { ... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
下面开始介绍消息机制中几个重要的类。
ThreadLocal
ThreadLocal类通过泛型可以存储任何类型的对象,它内部主要的方法为set()和get()方法。它通过set()方法存储对象,通过get()方法拿到存储的对象。ThreadLocal类的特点是它会以线程为作用域来存储数据,比如我们创建一个ThreadLocal对象来存储Integer类型数据,首先我们在主线程中给这个ThreadLocal对象赋值为10,在子线程1中给这个对象赋值为20,在子线程2中不进行赋值操作,然后分别打印ThreadLocal中存储的数据的值。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //在主线程中赋值为10 mThreadLocal.set(10); //打印结果 Log.e("test","Thread*main mThreadLocal:"+mThreadLocal.get()); new Thread("Thread*1"){ @Override public void run() { super.run(); //在线程1中赋值为20 mThreadLocal.set(20); //打印结果 Log.e("test","Thread*1 mThreadLocal:"+mThreadLocal.get()); } }.start(); new Thread("Thread*2"){ @Override public void run() { super.run(); //在线程2中不进行赋值操作,打印结果 Log.e("test","Thread*2 mThreadLocal:"+mThreadLocal.get()); } }.start(); }
最终得到的结果如下:
11-19 23:13:42.422 15945-15945/com.weimore.demo1 E/test: Thread*main mThreadLocal:10 11-19 23:13:42.425 15945-15965/com.weimore.demo1 E/test: Thread*1 mThreadLocal:20 11-19 23:13:42.425 15945-15966/com.weimore.demo1 E/test: Thread*2 mThreadLocal:null
可见在不同的线程中,对同一个ThreadLoccal对象进行赋值,但最后的结果是不同的。ThreadLocal以线程为作用域来存储对象,保证了对象在不同线程中的数据独立性和唯一性。当然,要想真正了解ThreadLocal是如何做到分线程存储数据的,还是得看set()和get()方法的源码。
在set()方法中,首先会获得当前线程对象,然后调用getMap()方法,在getMap()方法中其实就是获取当前线程中的成员变量threadlocals,它是一个ThreadLocalMap对象,其实就是以Map的形式存储数据。如果该对象存在,则将数据存入,否则就创建该对象,然后再存入数据。所以通过set()方法,我们知道了,数据其实最终是保存在当前线程的一个成员变量threadlocals中。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
而get()方法其实就是从获取当前所在线程的threadlocals对象,然后从中取出存储的数据。如果之前没有通过ThreadLocal存储过数据,则返回null。
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
因此,我们知道了,原来通过ThreadLocal存储数据,最终其实存储在线程的成员变量中的,所以我们在不同的线程中取得的数据不是同一个对象,所以赋值过后,得到的结果会不同。
ThreadLocal类在消息机制中主要是负责存储Looper的,系统通过ThreadLocal来存储和获得Looper对象,从而保证每个线程的Looper是独立且唯一的。
MessageQuery
MessageQuery虽然叫做消息队列,但其实内部是以单链表的方式存储消息的。MessageQuery是在Looper初始化时被创建的,它负责和Looper,Handler一起协作来维持整个消息机制的运作。MessageQuery中重要的方法有两个:enqueueMessage(),next()。
enqueueMessage()方法的作用是插入一个消息到链表中,当Handler调用sendMessage()方法时会调用enqueueMessage()方法。而next()方法的作用就是循环地从链表中返回和移除消息并交由Handler去处理消息,该方法会Looper的loop()方法中被调用。
Handler
Handler的使用我们已经非常熟悉了,Handler中比较常用的方法就是post()或postDelayed()方法,还有sendMessage()和sendMessageDelayed()方法等,其实这四个方法最后都是调用sendMessageDelayed()方法,而sendMessageDelayed()方法又是调用sendMessageAtTime(方法。在sendMessageAtTime()中我们发现了如下代码:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
可以看到这里调用了enqueueMessage()方法,并将mQueue对象传入其中,可以看到这个mQueue是一个MessgaeQuery对象,那么它是从何而来?难道Handler的内部也有一个MessagerQuery队列?我们查看Handler初始化的方法,发现这个mQueue是从当前线程的Looper中得到的。
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
那么Handler的enqueueMessage()方法到底做了什么呢?
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
我们看它的源码,发现在该方法中Handler将自己存储在了Message对象的成员变量target中,然后调用用了消息队列的enqueueMessage()方法将Message对象传入。前面已经说过,MessageQuery中的enqueueMessage()负责将消息存入消息队列中,而这里就是该方法具体被调用的地方。也就是说我们在使用Handler的sendMessage()方法时,其实是将Handler自身存入了消息中,然后再将消息存入了Looper中的消息队列中。
Handler中还有一个比较重要的方法就是dispatchMessage()方法。该方法会在Looper的方法中被调用,它的主要作用是将从消息队列中取出的消息发送到Handler的handlerMessage()方法中。如下:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
Looper
Looper算是消息机制的核心类,它负责创建消息队列,以及开启循环。当消息队列中有消息存在时,就将消息从消息队列中取出,并交由Handler处理。因此一个线程中如果想要使用Handler,就必须先创建Looperd对象,否则无法开启消息循环,更别说发送接收消息。又因为一个线程中不能存在多个消息队列,所以每个线程中Looper只允许被创建一次。而主线程中因为已经在内部创建过Looper对象了,所以可以直接使用Handler,如果在主线程再创建Looper实例的化,就会报Looper重复创建的异常。
Looper类中主要的方法有以下几个:prepare(),getMainLooper(),loop(),myLooper()。
其中getMainLooper()方法可以获取到主线程的Looper对象,myLooper()方法可以获取到当前线程的Looper对象,我们可以通过对比这两个方法得到的Looper对象是否为同一个对象,从而判断当前线程是否是主线程。
在Looper.prepare()方法中,主要负责创建Looper对象和创建消息队列。我们找到prepare()方法的源码:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
可以看到,首先要判断当前线程是否已拥有Looper对象。这里的sThreadLocal其实就是ThreadLocal类的对象。如果发现ThreadLocal对象在当前线程中已经存储过Looper了,说明Looper被重复创建了,则抛出“only one Looper may be created per thread”异常,如果ThreadLocal中还未存储Looper,则创建Looper,并存储到ThreadLocal中。
我们再接着看Looper的构造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在Looper的构造方法中会创建一个MessageQuery(消息队列),并将当前线程赋值给mThread变量。这里我们可以知道消息队列确实是由Looper创建的,如果不创建Looper,消息队列就不存在。
我们接着看Looper的最后一个也是最重要的方法loop()。loop()方法源码如下:
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
可以看到在loop()方法中存在一个无限for循环,它会不断的调用MessageQuery的next()方法,从消息队列中取出消息,而在下面还调用了msg.target的diapatchMessage()方法用来处理取出的消息。前面分析Handler时说到过,这个msg.target其实就是Handler自身,所以其实这里就是调用Handler的dispatchMessage()方法。上面也说了Handler的despatchMessage()方法最终会将消息发送到handlerMessage()中去处理。这里要注意的是,我们用Handler发送消息时可能是在别的线程中发送的,但最后消息是在Looper的loop()方法中被取出然后处理的,最后的handlerMessage()方法一定是在Looper所在的线程中被执行的。
由Handler引发的内存泄漏
我们先来看一下平常我们在Activity中创建Handler可能会造成内存泄漏的写法:
public class MainActivity extends AppCompatActivity { private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //发送延迟消息 handler.sendEmptyMessageDelayed(0, 1000 * 6); } }
像上面这种情况就非常由可能会造成内存泄漏。我们可以看到,上面在Activity中创建Handler,其实是使用了匿名内部类的方法创建的Handler对象。在Java中,非静态内部类(包括匿名内部类)是会持有外部类的引用的,所以这时的Handler就持有了Activity的引用。
根据前面我们了解的Android的消息机制,明白了在主线程中创建Handler后,Handler发送的消息会被发送到主线程的Looper所创建的消息队列中,这时主线程的MessageQuery就持有了Message的引用,另外由于Message中的target变量其实就是Handler自身,所以Message其实也持有了Handler的引用。这样就导致了也许Handler正在发送或处理延迟消息时,Activity被销毁了,但Activity的引用被Handler所持有,所以造成了内存泄漏。
ActivityThread -> Looper -> MessageQuery -> Message -> Handler ->Activity
解决这种内存泄漏的方法有几种:
首先最简单的,就是将 Handler 声明为静态内部类,这样Handler就不会持有Activity的引用了,当然如果你确实需要Activity的引用来处理某些东西,那就使用WeakReference,让Handler持有Activity的弱引用,这样也不会造成内存泄漏。还有一种方法就是在Activity被销毁的时候调用Handler.removeCallbacksAndMessages()方法,移除消息队列中的所有消息和回调,这样就不会使Handler被MessageQuery持有引用,也就不会造成内存泄漏了。