概述:
在线程之间通信时,通常会使用到Handler。与之相关联的类有:Message,Looper,MessageQueue。简单讲它们的作用分别为:
Handler:发送消息并且处理消息的对象。
Message:Handler接收和处理的对象。
Looper:每一个线程只能拥有一个Looper对象。它的loop()方法会从MessageQueue中读取消息,并且将读到的消息发送给该消息的Handler进行处理。
MessageQueue:消息队列,它采用先进先出的方式管理消息(Message)。在Looper对象初始化时会创建MessageQueue的实例。Looper的构造方法如下:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }从这里可以看出,在Looper初始化时,会创建一个与之相关联的MessageQueue对象。
在Looper中,有两个方法最重要:prepare()用来创建Looper对象,并与线程关联的;loop()用于从与Looper对象关联的MessageQueue中取出消息(Message)。
创建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)); }
首先说一下sThreadLocal,它是一个ThreadLocal<Looper>对象。而ThreadLocal是一个很特殊的全局变量,它的全局性只局限于当前的线程,外界所有的线程(包括处于同一个进程中的)都无法访问到它。
从代码中可以看出,首先从sThreadLocal中取值,如果有值说明当前线程已经被关联过Looper对象,此时就直接抛异常;如果没有,那就新new一个Looper对象,并且设置到sThreadLocal中。通过此种方法就可以保证一个线程只能拥有一个Looper对象。prepare()也只做了一件事:在允许的情况下,为当前线程关联一个Looper对象。
又由于一个线程只能关联一个Looper对象,所以一个线程prepare()只能被调用一次。
loop()方法使用一个死循环来不断取出存储在MessageQueue中的消息,并将消息交给该消息对应的handler进行处理。大体代码如下:
/** * Run the message queue in this thread. Be sure to call {@link #quit()} to * end the 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."); } //取出Looper关联的MessageQueue对象 final MessageQueue queue = me.mQueue; … for (;;) { //从MessageQueue中取出下一条消息(Message),该方法是阻塞的 Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; } … //将取到的消息进行分发 msg.target.dispatchMessage(msg); … msg.recycle();//回收该消息 } }从中可以看出,loop()用for(;;)进行了一个无限循环,也就是说:这个loop()方法会进行不断地读取消息,直到取到或者MessageQueue被放弃。
当Looper取到消息时,会调用msg.target.dispatchMessage()(就是发送msg的handler中的dispatchMessage())。由于Looper.loop()是在Looper对象所处的线程中执行的,所以handler.dispatchMessage()的运行线程与Looper对象所属的线程是同一个,而不一定与handler所处的是同一个线程。
如果想获取当前线程中的Looper对象,可以调用Looper.myLooper(),它会直接返回sThreadLocal.get();。
对于一个Message实例来说,包含好几个实例变量。其中有三个是最重要的:
1,what 用户定义的int型消息代码,用于区别不同的消息
2,obj 随消息传递的对象,用于传递数据
3,target 处理消息的handler。在上面的loop()代码中,就是调用msg.target.dispatchMessage()将消息传递回handler中。
对于Message实例的获取,通常通过handler.obtainMessage()及重载方法完成。在obtainMessage()的源码中可以发现,它里面只是调用了Message.obtain(this),这里的this指的就是调用obtainMessage()的Handler对象。下面看一下obtain()的源码:
public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; }这里面直接将Message的target属性设置成了当前的handler。这样在Looper.loop()进行消息分发时就可以找到对象的Handler对象了。
它的作用只有两个:发送Message和处理Message。程序使用Handler发送消息时,被发送的消息必须被送到指定的MessageQueue中。也就是说:如果希望Handler能够正常工作,必须在当前线程中有一个MessageQueue,否则消息就没地方存储,而MessageQueue是由Looper在构造时创建的。因此,为保证handler正常工作,它所在的线程必须有一个Looper对象。这里可以分成两种情况:
第一种:当在UI线程中使用Handler。由于主线程已经创建了Looper,所以可以直接使用。
第二种:当在非UI线程中时,必须自己创建一个Looper对象,并且将该Looper绑定到当前线程中(调用Looper.prepare()),并启动它(Looper.loop())。通过上面Message与Looper的分析,发现Message最终会传递到Handler的dispatchMessage()中,它的代码如下:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }从这一段代码可以看出,我们一般只需要重写Handler.handleMessage()就会收到在别处发的消息。
下面再看一下Handler构造方法,在构造方法中有两个是最重要的:获取当前Handler关联的Looper以及存储handler发送消息的MessageQueue。虽然handler的构造方法有多个,但是最终会执行下面两个中的一个:
//不传入Looper对象时 public Handler(Callback callback, boolean async){ … mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; … } //传入Looper对象时 public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
当不传入Looper对象时,与handler关联的Looper便是当前线程中存储的Looper实例。因为,如果当前线程没有Looper实例时,便会抛出异常。在UI线程中,系统已经为该线程关联了一个Looper对象,故可以直接使用new Handler();但在非UI线程中,我们需要在实例化handler之前调用Looper.prepare()为当前线程关联一个Looper对象。
当传入了Looper对象时,Handler关联的便是传入的Looper对象。此时handler发送的消息便会存储到该Looper对象的MessageQueue中。然后Looper通过loop()不断地从自己的MessageQueue中取出消息,再进行分发。
记传入的Looper对象所在的线程为TA,Handler实例所处的线程为TB。当TA与TB不一样时,handler一样可以将消息发往looper的MessageQueue中。由于Looper.loop()运行在TA线程中,因此loop()方法中调用的msg.target.dispatchMessage()一样是运行在TA线程中,而不是运行在TB中。再结合dispatchMessage()的源码可知,此时Handler.handleMessage()也是在TA中的。
looper取到的msg始终是与自己关联的MessageQueue中的,所以MessageQueue处于哪个线程中,就意味着它里面的msg会被哪个线程中的looper.loop()取到,进而决定了Hanlder.handleMessage()运行在哪个线程中。但是由于MessageQueue经常由Looper.myQueue()获取,所以Looper对象也就决定了Handler.handleMessage()运行的线程.
上面TB中hanlder发送的消息会存储到TA中的MessageQueue中,TA中looper会不断从TA中MessageQueuek 取消息,某一刻会取到TB中handler发送的msg,从而会在TA中调用TB中Handler.handleMessage()。
因此,可以总结一句:与handler关联的Looper对象决定了Handler.handleMessage()执行时所处的线程,或者更准确地说,handler发送的消息存储到的MessageQueue决定了Handler.handleMessage()执行时所处的线程。当Looper实例处于UI线程中,不管handler位于哪个线程,都可以在Handler.handleMessage()中更新界面,因为这个方法是在UI线程中执行的。示例如下:
tv = (TextView) findViewById(R.id.tv); new Thread() { public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Looper.prepare(); System.out.println("线程:" + Thread.currentThread().getName()); //new handler时传入的Looper是UI线程中的 Handler subHandler = new Handler(getMainLooper()) { public void handleMessage(Message msg) { System.out.println("线程:" + Thread.currentThread().getName()); tv.setText("我是在run中运行的"); }; }; subHandler.sendEmptyMessage(0); Looper.loop(); }; }.start();在上述代码中,subHandler是处于子线程中的,但是handleMessage()是执行在UI线程中的,所以代码不会报错,而且也会更新成功。但是,如果我们在Handler的实例化时不传入getMainLooper(),则会崩掉,异常就是不能在子线程中更改UI。至于为什么要在前面加上Thread.sleep(2000),参看碎雨(四)中的"非UI线程更新UI"。
在Handler中常用sendEmptyMessage和sendMessage发送消息,两者最终会走到sendMessageAtTime(Message, long)中。sendMessageAtTime()的代码为:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue;//首先获取MessageQueue 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); }这里的mQueue都是通过Looper.mQueue属性获得的。 也就是说,handler发送的消息最终会存储到与之相关联的Looper对象的MessageQueue中,而Looper.loop()也是从与自己相关联的MessageQueue中获取的Message对象。从而保证了handler发送的消息会被正确的取出来。
其中enqueueMessage()的代码为:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this;//在这里将Message.target的属性给设置上了 if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
在发送消息时,除了send*系列方法,还有一个post()。源码如下:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }从中可以看出,post()也是将参数Runnable封装成Message然后发送出去。此时该Message的callback变量就是Runnable对象。
再结合Handler.dispatchMessage()可以看出,收到该消息后只会运行Message.callback,并不会执行Handler类中的handleMessage()等。
private static void handleCallback(Message message) { message.callback.run(); }在该方法中,终于执行了post()中传入的Runnable对象的run()。
在上面的handler.sendMessage()代码中,最终会调用到MessageQueue.enqueueMessage(),大体代码如下:
boolean enqueueMessage(Message msg, long when) { …… synchronized (this) { …… msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { ……// Inserted within the middle of the queue. Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } …… } return true; }从中可以发现,它是通过Message.next将整个Message串成一连Message链,每一个Message都通过next属性记住它的下一个Message。这就是MessageQueue存储Message的过程。
handler把消息发送到与之关联的looper中的MessageQueue中。Looper.loop()会不断地从MessageQueue中取新消息,并将取到的消息传递到Handler.dispatchMessage()中,从而将消息又传回handler中。
message能正确地返回到发送它的handler对象中,是因为message.target变量记录下了handler对象。
由于looper对象决定了handleMessage()调用的线程,所以Handler可以用于线程之间的通信。比如在子线程中访问网络,然后通过handler将结果返回到UI线程,进而更新UI。
一个线程最多只能关联一个Looper对象,一个Looper对象对应一个MessageQueue对象,而MessageQueue中可以存储多条不同的Message,每一个Message最多只能指定一个Handler进行处理。因此,线程与Handler是一对多的关系。
当在子线程中使用Handler时,需要为该线程关联一个Looper对象。此时可以使用HandlerThread类,它本身就是一个Thread的子类,它的run()方法如下:
@Override public void run() { mTid = Process.myTid(); Looper.prepare();//为该线程关联一个Looper对象 synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop();//启动关联的looper对象 mTid = -1; }
从中可以看出HandlerThread已经调用过了Looper.prepare()和Looper.loop()。因此,在调用start()之后可以直接使用Handler。
在调用Looper.loop()之前,调用了onLooperPrepared(),这是一个空方法,一般会在该方法中添加执行一些设置。比如,初始化Handler之类的。
由于onLooperPrepare()是在run()方法中调用的,所以onLooperPrepared()运行在子线程中,因此可以在里面进行一些访问网络之类的操作。