转载一篇我以前发在csdn上的博客。java
在咱们去讨论Handler,Looper,MessageQueue的关系以前,咱们须要先问两个问题:android
这一套东西搞出来是为了解决什么问题呢?程序员
若是让咱们来解决这个问题该怎么作?网络
以上者两个问题,是我最近总结出来的,在咱们学习了解一个新的技术以前,最好是先能回答这两个问题,这样你才能对你正在学习的东西有更深入的认识。异步
第一个问题:google的程序员们搞出这一套东西是为了解决什么问题的?这个问题很显而易见,为了解决线程间通讯的问题。咱们都知道,Android的UI/View这一套系统是运行在主线程的,而且这个主线程是死循环的,来看看具体的证据吧。async
public final class ActivityThread { public static void main(String[] args) { //... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
如上面的代码示例所示,ActivityThread.main()
方法做为Android程序的入口,里面我省略了一些初始化的操做,而后就执行了一句Looper.loop()
方法,就没了,再下一行就抛异常了。ide
loop()
方法里面实际上就是一个死循环,一直在执行着,不断的从一个MQ(MessageQueue,后面我都缩写成MQ了)去取消息,若是有的话,那么就执行它或者让它的发送者去处理它。函数
通常来讲,主线程循环中都是执行着一些快速的UI操做,当你有手touch屏幕的时候,系统会产生事件,UI会处理这些事件,这些事件都会在主线程中执行,并快速的响应着UI的变化。若是主线程上发生一些比较耗时的操做,那么它后面的方法就没法获得执行了,那么就会出现卡顿,不流畅。oop
所以,Android并不但愿你在主线程去作一些耗时的操做,这里对“耗时”二字进行朴素的理解就好了,就是执行起来须要消耗的时间比较多的操做。好比读写文件,小的文件也许很快,但你没法预料文件的大小,再好比访问网络,再好比你须要作一些复杂的计算等等。学习
为了避免阻碍主线程流畅的执行,咱们就必须在须要的时候把耗时的操做放到其余线程上去,当其余线程完成了工做,再给一个通知(或许还带着数据)给到主线程,让主线程去更新UI什么的,固然了,若是你要的耗时操做只是默默无闻的完成就好了,并不须要通知UI,那么你彻底不须要给通知给到UI线程。这就是线程间的通讯,其余线程作耗时操做,完成了告诉UI线程,让它进行更新。为了解决这个问题,Android系统给咱们提供了这样一套方案来解决。
第二个问题:若是让咱们来想一套方案来解决这个线程间通讯的问题,该怎么作呢?
先看看咱们如今已经有的东西,咱们有一个一直在循环的主线程,它实现起来大概是这个样子:
public class OurSystem { public static void main(String [] args) { for (;;) { //do something... } } }
为何主线程要一直死循环的执行呢?
关于这一点,我我的并无特别透彻的认知,但我猜想,对于有GUI的系统/程序,应该都有一个不断循环的主线程,由于这个GUI程序确定是要跟人进行交互的,也就是说,须要等待用户的输入,好比触碰屏幕,动动鼠标,敲敲键盘什么的,这些事件确定是硬件层先得到一个响应/信号,而后会不断的向上封装传递。
若是说咱们一碰屏幕,一碰鼠标,就开启一个新线程去处理UI上的变化,首先,这固然是能够的!UI在什么线程上更新其实都是能够的嘛,并非说必定要在主线程上更新,这是系统给我设的一个套子。而后,问题也会复杂的多,若是咱们快速的点击2下鼠标,那么一瞬间就开启了两个新线程去执行,那么这两个线程的执行顺序呢?两个独立的线程,咱们是没法保证说先启动的先执行。
因此第一个问题就是执行顺序的问题。
第二个问题就是同步,几个相互独立的线程若是要处理同一个资源,那么形成的结果都是使人困惑,不受控制的。另外一方面强行给全部的操做加上同步锁,在效率上也会有问题。
为了解决顺序执行的问题,很是容易就想到的一种方案是事件队列,各类各样的事件先进入到一个队列中,而后有个东西会不断的从队列中获取,这样第一个事件必定在第二个事件以前被执行,这样就保证了顺序,若是咱们把这个“取事件”的步骤放在一个线程中去作,那么也顺便解决了资源同步的问题。
所以,对于GUI程序会有一个一直循环的(主)线程,可能就是这样来的吧。
这是一个很是纯净的死循环,咱们想要作一些事情的话,就得让它从一个队列里面获取一些事情来作,就像打印机同样。所以咱们再编写一个消息队列类,来存放消息。消息队列看起来应该是这样:
public class OurMessageQueue() { private LinkedList<Message> mQueue = new LinkedList<Message>(); // 放进去一条消息 public void enQueue() { //... } // 取出一条消息 public Message deQueue() { //... } // 判断是否为空队列 public boolean isEmpty() { //... } }
接下来咱们的循环就须要改形成能从消息队列里获取消息,并可以根据消息来作些事情了:
public class OurSystem { public static void main(String [] args) { // 初始化消息队列 OurMessageQueue mq = ... for (;;) { if (!mq.isEmpty()) { Message msg = mq.deQueue(); //do something... } } } }
如今咱们假象一下,咱们须要点击一下按钮,而后去下载一个超级大的文件,下载完成后,咱们再让主线程显示文件的大小。
首先,按一下按钮,这个事件应该会被触发到主线程来(具体怎么来的我还尚不清楚,但应该是先从硬件开始,而后插入到消息队列中,主线程的循环就能获取到了),而后主线程开启一个新的异步线程来进行下载,下载完成后再通知主线程来更新,代码看上去是这样的:
// 脑补的硬件设备…… public class OurDevice { // 硬件设备可能有一个回调 public void onClick() { // 先拿到同一个消息队列,并把咱们要作的事情插入队列中 OurMessageQueue mq = ... Message msg = Message.newInstance("download a big file"); mq.enQueue(msg); } }
而后,咱们的主线程循环获取到了消息:
public class OurSystem { public static void main(String [] args) { // 初始化消息队列 OurMessageQueue mq = ... for (;;) { if (!mq.isEmpty()) { Message msg = mq.deQueue(); // 是一条通知咱们下载文件的消息 if (msg.isDownloadBigFile()) { // 开启新线程去下载文件 new Thread(new Runnable() { void run() { // download a big file, may cast 1 min... // ... // ok, we finished download task. // 获取到同一个消息队列 OurMessageQueue mq = ... // 消息入队 mq.enQueue(Message.newInstance("finished download")); } }).start(); } // 是一条通知咱们下载完成的消息 if (msg.isFilishedDownload()) { // update UI! } } } } }
注意,主线程循环获取到消息的时候,显示对消息进行的判断分类,不一样的消息应该有不一样的处理。在咱们获取到一个下载文件的消息时,开启了一个新的线程去执行,耗时操做与主线程就被隔离到不一样的执行流中,当完成后,新线程中用同一个消息队列发送了一个通知下载完成的消息,主线程循环获取到后,里面就能够更新UI。
这样就是一个我随意脑补的,简单的跨线程通讯的方案。
有以下几点是值得注意的:
主线程是死循环的从消息队列中获取消息。
咱们要将消息发送到主线程的消息队列,咱们须要经过某种方法能获取到主线程的消息队列对象
消息(Message)的结构应该如何设计呢?
Android中有一个Looper
对象,顾名思义,直译过来就是循环的意思,Looper
也确实干了维持循环的事情。
Looper的代码是很是简单的,去掉注释也就300多行。在官方文档的注释中,它推荐咱们这样来使用它:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }
先来看看prepare方法干了什么。
Looper.prepare()
public static void prepare() { prepare(true); } 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)); }
注意prepare(boolean)
方法中,有一个sThreadLocal
变量,这个变量有点像一个哈希表,它的key是当前的线程,也就是说,它能够存储一些数据/引用,这些数据/引用是与当前线程是一一对应的,在这里的做用是,它判断一下当前线程是否有Looper
这个对象,若是有,那么就报错了,"Only one Looper may be created per thread",一个线程只容许建立一个Looper
,若是没有,就new一个新的塞进这个哈希表中。而后它调用了Looper
的构造方法。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在Looper
的构造方法中,很关键的一句,它new了一个MessageQueue
对象,并本身维持了这个MQ的引用。
此时prepare()
方法的工做就结束了,接下来须要调用静态方法loop()
来启动循环。
Looper.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; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); //... } }
loop()方法,我作了省略,省去了一些不关心的部分。剩下的部分很是的清楚了,首先调用了静态方法myLooper()获取一个Looper对象。
public static Looper myLooper() { return sThreadLocal.get(); }
myLooper()
一样是静态方法,它是直接从这个ThreadLocal
中去获取,这个刚刚说过了,它就相似于一个哈希表,key是当前线程,由于刚刚prepare()
的时候,已经往里面set了一个Looper
,那么此时应该是能够get到的。拿到当前线程的Looper
后,接下来,final MessageQueue queue = me.mQueue;
拿到与这个Looper
对应的MQ,拿到了MQ后,就开启了死循环,对消息队列进行不停的获取,当获取到一个消息后,它调用了Message.target.dispatchMessage()
方法来对消息进行处理。
Looper的代码看完了,咱们获得了几个信息:
Looper
调用静态方法prepare()
来进行初始化,一个线程只能建立一个与之对应的Looper
,Looper
初始化的时候会建立一个MQ,所以,有了这样的对应关系,一个线程对应一个Looper
,一个Looper
对应一个MQ。能够说,它们三个是在一条线上的。
Looper
调用静态方法loop()
开始无限循环的取消息,MQ调用next()
方法来获取消息
对于MQ的源码,简单的看一下,构造函数与next()
方法就行了。
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
MQ的构造方法简单的调用了nativeInit()来进行初始化,这是一个jni方法,也就是说,多是在JNI层维持了它这个消息队列的对象。
MessageQueue.next()
Message next() { final long ptr = mPtr; if (ptr == 0) { return null; } int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } } } }
next()
方法的代码有些长,我做了一些省略,请注意到,这个方法也有一个死循环,这样作的效果就是,在Looper
的死循环中,调用了next()
,而next()
这里也在死循环,表面上看起来,方法就阻塞在Looper
的死循环中的那一行了,知道next()
方法能返回一个Message
对象出来。
简单浏览MQ的代码,咱们获得了这些信息:
MQ的初始化是交给JNI去作的
MQ的next()
方法是个死循环,在不停的访问MQ,从中获取消息出来返回给Looper
去处理。
Message
对象是MQ中队列的element,也是Handler
发送,接收处理的一个对象。对于它,咱们须要了解它的几个成员属性便可。
Message
的成员变量能够分为三个部分:
数据部分:它包括what(int)
, arg1(int)
, arg2(int)
, obj(Object)
, data(Bundle)
等,通常用这些来传递数据。
发送者(target):它有一个成员变量叫target
,它的类型是Handler
的,这个成员变量很重要,它标记了这个Message
对象自己是谁发送的,最终也会交给谁去处理。
callback:它有一个成员变量叫callback
,它的类型是Runnable
,能够理解为一个能够被执行的代码片断。
Handler
对象是在API层面供给开发者使用最多的一个类,咱们主要经过这个类来进行发送消息与处理消息。
一般咱们调用没有参数的构造方法来进行初始化,使用起来大概是这样的:
Handler mHandler = new Handler() { handleMessage(Message msg) { //... } }
没有参数的构造方法最终调用了一个两个参数的构造方法,它的部分源码以下:
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; mCallback = callback; mAsynchronous = async; }
注意到,它对mLooper
成员变量进行了赋值,经过Looper.myLooper()
方法获取到当前线程对应的Looper
对象。上面已经提到过,若是Looper
调用过prepare()
方法,那么这个线程对应了一个Looper
实例,这个Looper
实例也对应了一个MQ,它们三者之间是一一对应的关系。
而后它经过mLooper
对象,获取了一个MQ,存在本身的mQueue
成员变量中。
Handler
的初始化代码说明了一点,Handler
所初始化的地方(所在的线程),就是从将这个线程对应的Looper
的引用赋值给Handler
,让Handler
也持有
对于主线程来讲,咱们在主线程的执行流中,new一个Handler
对象,Handler对象都是持有主线程的Looper
(也就是Main Looper
)对象的。
一样的,若是咱们在一个新线程,不调用Looper.prepare()
方法去启动一个Looper
,直接new一个Handler
对象,那么它就会报错。像这样
new Thread(new Runnable() { @Override public void run() { //Looper.prepare(); //由于Looper没有初始化,因此Looper.myLooper()不能获取到一个Looper对象 Handler h = new Handler(); h.sendEmptyMessage(112); } }).start();
以上代码运行后会报错:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
小结:Handler
的初始化会获取到当前线程的Looper
对象,并经过Looper
拿到对应的MQ对象,若是当前线程的执行流并无执行过Looper.prepare()
,则没法建立Handler对象
Handler.sendMessage()
sendMessage
消息有各类各样的形式或重载,最终会调用到这个方法:
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
方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
注意到它对Message
的target
属性进行了赋值,这样这条消息就知道本身是被谁发送的了。而后将消息加入到队列中。
Handler.dispatchMessage()
Message
对象进入了MQ后,很快的会被MQ的next()
方法获取到,这样Looper
的死循环中就能获得一个Message
对象,回顾一下,接下来,就调用了Message.target.dispatchMessage()
方法对这条消息进行了处理。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } public void handleMessage(Message msg) { //这个方法是空实现,让客户端程序员去覆写实现本身的逻辑 }
dispatchMessage
方法有两个分支,若是callback
(Runnable
)不是null
,则直接执行callback.run()
方法,若是callback
是null
,则将msg
做为参数传给handleMessage()
方法去处理,这样就是咱们常见的处理方法了。
Message.target与Handler
特别须要注意Message
中的target
成员变量,它是指向本身的发送者,这一点意味着什么呢?
意味着:一个有Looper
的线程能够有不少个Handler
,这些Handler
都是不一样的对象,可是它们均可以将Message
对象发送到同一个MQ中,Looper
不断的从MQ中获取这些消息,并将消息交给它们的发送者去处理。一个MQ是能够对应多个Handler
的(多个Handler
均可以往同一个MQ中消息入队)
下图能够简要的归纳下它们之间的关系。
Looper
调用prepare()
进行初始化,建立了一个与当前线程对应的Looper
对象(经过ThreadLocal
实现),而且初始化了一个与当前Looper
对应的MessageQueue
对象。
Looper
调用静态方法loop()
开始消息循环,经过MessageQueue.next()
方法获取Message
对象。
当获取到一个Message
对象时,让Message
的发送者(target
)去处理它。
Message
对象包括数据,发送者(Handler
),可执行代码段(Runnable
)三个部分组成。
Handler
能够在一个已经Looper.prepare()
的线程中初始化,若是线程没有初始化Looper
,建立Handler
对象会失败
一个线程的执行流中能够构造多个Handler
对象,它们都往同一个MQ中发消息,消息也只会分发给对应的Handler
处理。
Handler
将消息发送到MQ中,Message
的target
域会引用本身的发送者,Looper
从MQ中取出来后,再交给发送这个Message
的Handler
去处理。
Message
能够直接添加一个Runnable
对象,当这条消息被处理的时候,直接执行Runnable.run()
方法。