概述
在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信。
从技术实现上来说,消息机制还是比较简单的。从大的方面讲,不光是Android平台,各种平台的消息机制的原理基本上都是相近的,其中用到的主要概念大概有:
- 消息发送者;
- 消息队列;
- 消息处理循环。
示意图如下:

图中表达的基本意思是,消息发送者通过某种方式,将消息发送到某个消息队列里,同时还有一个消息处理循环,不断从消息队列里摘取消息,并进一步解析处理。
在Android平台上,把上图的左边部分包装成了一个Looper类,这个类的内部具有对应的消息队列(MessageQueue mQueue)和loop函数。
但是Looper只是个简单的类而已,它虽然提供了循环处理方面的成员函数loop(),却不能自己凭空地运行起来,而只能寄身于某个真实的线程。而且,每个线程最多只能运作一个Looper对象,这一点应该很容易理解。
Android平台上另一个关键类是Handler。当消息循环在其寄身的线程里正式运作后,外界就是通过Handler向消息循环发出事件的。当然,系统也允许多个Handler向同一个消息队列发送消息:
整个消息机制的轮廓也就是这些啦,下面我们来详细阐述。
Looper
Looper类的定义截选如下:
frameworks/base/core/java/android/os/Looper.java
1 | public final class Looper { |
我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare() Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){…}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){…}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。
Handler
一般而言,运作Looper的线程会负责构造自己的Handler对象,当然,其他线程也可以针对某个Looper构造Handler对象。
Handler对象在构造时,不但会把Looper对象记录在它内部的mLooper成员变量中,还会把Looper对象的消息队列也一并记录,代码截选如下:
1 | public Handler(Callback callback, boolean async) |
我们也可以直接传入Looper对象,此时可以使用另一个构造函数:
1 | public Handler(Looper looper, Callback callback, boolean async) |
以后,每当线程需要向消息队列发送消息时,只需调用Handler对象的sendMessage()等成员函数就可以了。
sendMessage()最终调用的是sendMessageAtTime()。
1 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { |
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
简单说来,只要一个线程可以获取另一个目标线程的某个Handler对象,它就具有了向目标线程发送消息的能力。不过,也只是发送消息而已,消息的真正处理却是在目标线程的消息循环里完成的。
前文已经说过,在Looper准备停当后,我们的线程会调用Looper.loop(),从而进入真正的循环机制。
1 | public static void loop() { |
loop()函数的代码流程非常简单,只不过是在一个for循环里不停从消息队列中摘取消息,而后调用msg.target.dispatchMessage()对消息进行派发处理而已。
这么看来,msg.target域就显得比较重要了,说穿了,这个域记录的其实就是当初向消息队列发送消息的那个handler啦。请大家注意msg.target = this;一句,记录的就是handler对象。
在实际运用handler时,大体有两种方式。一种方式是写一个继承于Handler的新类,并在新类里实现自己的handleMessage()成员函数;另一种方式是在创建匿名Handler对象时,直接修改handleMessage()成员函数。
MessageQueue
MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。
在刚刚介绍Handler的sendMessageAtTime()时,我们已经看到最终会调用queue.enqueueMessage()来向消息队列打入消息。queue对应的类是MessageQueue,其定义截选如下:frameworks/base/core/java/android/os/MessageQueue.java
1 | public final class MessageQueue { |
其中Message mMessages记录的就是一条消息链表。另外还有几个native函数,这就说明MessageQueue会通过JNI技术调用到底层代码。mMessages域记录着消息队列中所有Java层的实质消息。请大家注意,记录的只是Java层的消息,不包括C++层的。MessageQueue的示意图如下:

Message
Message 很简单了,你想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。
源码分析
接下来我们会结合App主线程(UI线程)来讲解,从App启动后一步一步往下走分析整个Android的消息处理机制,首先在ActivityThread类有我们熟悉的main的函数,App启动的代码的入口就在这里,UI线程本来只是一个普通线程,在这里会把UI线程转换成Looper线程,什么是Looper线程,不急往下看就知道了。
1 | public final class ActivityThread { |
首先执行的是 Looper.prepareMainLooper() 我们来看下Looper里面的这个方法做了什么?
注:看之前先稍微了解下ThreadLocal是什么?
ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。具体看下ThreadLocal.java 的源码!
1 | public final class Looper { |
看了上面的代码(仔细看下注释),我们发现 Looper.prepareMainLooper()做的事件就是new了一个Looper实例并放入Looper类下面一个static的ThreadLocal<Looper> sThreadLocal静态变量中,同时给sMainLooper赋值,给sMainLooper赋值是为了方便通过Looper.getMainLooper()快速获取主线程的Looper,sMainLooper是主线程的Looper可能获取会比较频繁,避免每次都到 sThreadLocal 去查找获取。
接下来重点是看下Looper的构造函数,看看在new Looper的时候做了什么事?
1 | private Looper(boolean quitAllowed) { |
看到没有,这时候给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。执行完Looper.prepareMainLooper() 之后,主线程从普通线程转成一个Looper线程。目前的主线程线程已经有一个Looper对象和一个消息队列mQueue,引用关系如下图:(主线程可以轻松获取它的Looper,主线程的Looper持有主线程消息队列的引用)
具体如何获取主线程的Looper对象和消息列表呢?
1 | //在主线程中执行 |
接下来回到ActivityThread 的main函数,执行完Looper.prepareMainLooper() 之后下一句代码是ActivityThread thread = new ActivityThread();这句话就是创建一下ActivityThread对象,这边需要注意的时候ActivityThread并不是一个线程,它并没有继承Thread,而只是一个普通的类public final class ActivityThread{...}ActivityThread的构造函数并没有做什么事只是初始化了资源管理器。
1 | ActivityThread() { |
接着往下看下一行代码
1 | ActivityThread thread = new ActivityThread(); |
thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 Handler将Message发送给主线程,之后讲)。我们之前提到主线程最后会进入无限循环当中,如果没有在进入死循环之前创建其他线程,那么待会谁会给主线程发消息呢?,没错就是在这里创建了这个线程,这个线程会接收来自系统服务发送来的一些事件封装了Message并发送给主线程,主线程在无限循环中从队列里拿到这些消息并处理这些消息。(Binder线程发生的消息包括LAUNCH_ACTIVITY,PAUSE_ACTIVITY 等等)
继续回到mian 函数的下一句代码Looper.loop() 那么重点来了,我们来看下Looper.loop()的源码:
1 | public static void loop() { |
上面的代码,大家具体看下注释,这时候主线程(UI线程)执行到这一步就进入了死循环,不断地去拿消息队列里面的消息出来处理?那么问题来了
UI线程一直在这个循环里跳不出来,主线程不会因为Looper.loop()里的死循环卡死吗,那还怎么执行其他的操作呢?
在looper启动后,主线程上执行的任何代码都是被looper从消息队列里取出来执行的。也就是说主线程之后都是通过其他线程给它发消息来实现执行其他操作的。生命周期的回调也是如此的,系统服务ActivityManagerService通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。
主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他线程怎么办?
这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。
所以上面代码的重点是queue.next() 的函数,其他的我们就不多说了,我们来看下queue.next()的源码(主要还是看注释):
1 | Message next() |
nativePollOnce()很重要,是一个native的函数,在native做了大量的工作,主要涉及到epoll机制的处理(在没有消息处理时阻塞在管道的读端),具体关于native相关的源码本篇文章不涉及,感兴趣的同学可以网上找找,有不少分析得比较深。
分析到这里,从应用启动创建Looper,创建消息队列,到进入loop方法执行无限循环中,那么这一块就告一段落了,主线程已经在死循环里轮询等待消息了,接下来我们就要再看看,系统是怎么发消息给主线程的,主线程是怎么处理这些个消息的?
在准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(简称AMS)线程会通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity,看下scheduleLaunchActivity方法:
注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(在主线程进入死循环之前创建了这个线程)。
1 | //这个方法不是在主线程调用,是Binder线程下调用的 |
把启动一些信息封装成ActivityClientRecord之后,最后一句调用sendMessage(H.LAUNCH_ACTIVITY, r);我们接着往下看:
1 | private void sendMessage(int what, Object obj) { |
看到没有,最后启动Activity的信息都封装一个Message,但是这里有个问题了,之前在分析main函数的时候,完全没给出往主线程消息队列插入消息的方式,这里有了消息,但是怎么发到主线程的消息队列呢?最后一句又是重点mH.sendMessage(msg); mH 是什么呢?难道是Handler,我们来看下它是什么东西?
我们看了下ActivityThread 的成员变量,发现一句初始化的代码
1 | final H mH = new H(); |
继续往下看H是什么?
1 | public final class ActivityThread{ |
H 果不出其然是Handler,而且是ActivityThread的内部类,看了一下它的handleMessage 方法,LAUNCH_ACTIVITY、PAUSE_ACTIVITY、RESUME_ACTIVITY…好多好多,H 类帮我们处理了好多生命周期的事情。那么再回到mH.sendMessage(msg)这句代码上,在Binder线程执行mH.sendMessage(msg),由主线程创建的Handler mH实例发送消息到主线程的消息队列里,消息队列从无到有,主线程堵塞被唤醒,主线程loop拿到消息,并回调mH的handleMessage 方法处理LAUNCH_ACTIVITY 等消息。从而实现Activity的启动。
讲到这里,基本一个启动流程分析完了,大家可能比较想知道的是 mH.sendMessage(msg); 关于Hanlder是怎么把消息发到主线程的消息队列的?我们接下来就讲解下Handler,首先看下Handler的源码!我们先来看看我们经常用的Handler的无参构造函数,实际调用的是Handler(Callback callback, boolean async)构造函数
1 | public Handler() { |
上面的代码说明了下面几个问题:
- Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
- 如果该线程不是Looper线程,在这个线程new Handler 就会报错!
- 上面两点综合说明了下面一段很常见的代码:把普通线程转成Looper线程的代码,为什么在
Looper.prepare()和Looper.loop()中间要创建Handler:
1 | class LooperThread extends Thread { |
那么接下来,我们接着往下看Handler的sendMessage(msg)方法,这个方法也是比较重要的,也比较常用,Handler 有很多sendXXXX开头的方法sendMessageAtTime、sendEmptyMessageDelayed、sendEmptyMessage等等,都是用来给消息队列添加消息的,那么这些方法最终都会调用enqueueMessage来实现消息进入队列:
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
我们再来看下MessageQueue 的enqueueMessage(msg, uptimeMillis)方法:
1 | boolean enqueueMessage(Message msg, long when) { |
最后我们再看下Handler 的dispatchMessage方法,这个方法在Looper线程从消息队列拿出来的时候,通过msg.target.dispatchMessage(msg)调用的。
1 | /** |
到这里,整个Android的消息处理机制Java层内容基本讲解完毕了。
参考: