概述
提起 Handler,相信大家都不会陌生,日常开发中不可避免地要使用到它。今天要讲的是消息机制。消息机制?咋听起来有点抽象。其实我们在使用 Handler 的时候,就使用了 Android 的消息机制。
从开发的角度来看,Handler 是 Android 消息机制的上层接口,这使得开发过程中只需要设计这方面的内容即可。但是作为一名合格的开发者,弄清它的实现原理是很有必要的。
俗话说得好,一图胜千言,我们先来看下 Android 消息机制简单示意图(图片参考自这篇文章)。

我们把 Thread 比作是一个 发动机,MessageQueue 看作是一条流水线,Message 就像是流水线上的工人,Looper 是流水线下的滚筒,Handler 像是一个工人,它负责把 Message 这个产品送到流水线上,最后又负责把它取走。
这幅图中的各个组件的说明如下:
- Looper ==》 滚轮
- MessageQueue ==》 流水线
- Message ==> 流水线上的产品
- Handler ==》 扮演把『产品』送进流水线,完了以后又把『产品』取走的角色
- Thread ==》 动力
另外还有一个重要概念 ——ThreadLocal 图中没有标明出来。下面对各个部分进行详细介绍。
Android 的消息机制分析
从 Handler 出发
相信很多做 Android 开发的同学都写过与下面相似的代码。在子线程中做一些耗时操作,比如网络请求,操作完成之后,将返回的数据包装为 Message 对象然后调用 sendMessageXxx 方法,最后在 handleMessage 方法中对结果进行处理。
|
|
从 Handler.sendMessage(message) 到 Handler.handlerMessage方法经历了什么样的过程?
我们先看看 sendMessage 方法内部是怎么实现的。
|
|
整个调用流程是这样的 sendMessage ==》 sendMessageDelayed ==》 sendMessageAtTime ==》 enqueueMessage ==》 MessageQueue.enqueueMessage
我们可能还会调用 Handler.post(Runnable)方法到目标线程中执行 run 方法。post 方法会先调用 getPostMessage方法将 Runable 包装为 一个 Message 对象,(Runnable 就存储在 callback 中)。其他的 postXxx(Runnable)方法内部实现也是这样的流程,首先将 Runnable 包装为一个 Message 对象然后调用相应的 sendXxx 方法。
|
|
从上述的调用流程可以看出 sendXxx 或者 postXxx 方法最终都会调用 MessageQueue 的 enqueueMessage方法,将 Message 追加到 MessageQueue 中。
小结
- Handler 的消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现。
- post 系列方法最终是通过 send 的一系列方法来实现的。
Handler 的发送消息的过程仅仅是向消息队列插入了一条消息。
MessageQueue 对象是从哪里来的?
mQueue 是 Handler 的一个成员变量,它是在哪里初始化的呢?先看看 Handler 的构造方法
|
|
我们看到 mQueue 是从 Looper 中取出的。在解说 Looper 之前,我们先看看前面提到的 MessageQueue 。
MessageQueue 的工作原理
MessageQueue 主要包含两个操作:插入和读取(分别对应 enqueueMessage 和 next 方法)。
- 顾名思义,enqueueMessage 的作用是往队列中插入一条信息。
- next() 的作用是从队列中取出一条信息并将其从消息队列中移除。
虽然 MessageQueue 名为消息队列,但是它的内部实现并不是用队列,而是通过一个单链表的数据结构来维护消息列表。
- 为什么选择使用单链表结构? 因为 Message 是可以定时发送的,若使用普通的队列,当插入一个发送时间晚于队首 Message 发送时间的新 Message,那么就需要插队,实现起来不方便,而使用优先队列又显得比较复杂。因此就采用了单链表实现。
接下来我们重点看看 enqueueMessage 方法和 next 方法。
enqueueMessage 方法:
|
|
从代码中不难看出,enqueueMessaege 虽然有点长,但是逻辑还是比较清晰的。它先做了一些状态判断以及一些边界检测,完了以后再进行单链表的插入操作。
next 方法
|
|
next 方法中有一个死循环,其主要逻辑如下:
- 如果消息队列中没有消息,那么 next 会一直阻塞在那里。
- 当队首的消息设置了延迟执行时,会造成短时间的阻塞。
- 当有新消息到来时,next 方法会返回这条信息并将其从单链表中移除。
当调用了 quit 方法之后,mQuitting 为 true ,next 方法会移除 native 层的消息队列并返回 null。
整个 next 方法的逻辑如下: 从消息队列中依次取出消息。如果这个消息到了执行时间,那么就将该消息返回给 Looper,并且将消息队列链表的指针后移。实际上消息队列维护着一个分发屏障(dispatch barrier),当一个 Message 的时间戳低于这个值的时候,消息就会被分发给 Handler 进行处理。形象一点?看看下面这张图(图片来自 Android Handler Internals,侵删)

位于 dispatch barrier 左边的 Message 都被阻塞着,位于其右边的是即将分发的 Message。
我们是不是该说下 Looper 了,下下个就到它了,在此之前需要先看看 ThreadLocal 相关知识。
ThreadLocal 简介
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中才能获取到存储的数据,对于其他线程来说则无法获取到数据。
使用场景:
- 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑采用 ThrradLocal。
- 在 Android 中,Looper 类就是利用了 ThreadLocal 的特性,保证每个线程只存在一个 Looper 对象。
- 复杂逻辑下的对象传递。
- 比如监听器的传递。
从 ThreadLocal 的 set 和 get 方法 可以看出,它们所操作的对象都是当前线程的 localValues 对象的 table 数组,因此在不同的线程中访问同一个 ThreadLocal 的 get 和 set 方法,他们对 ThreadLocal 所做的读/写操作仅限于各自线程的内部。
关于 ThreadLocal 的具体介绍请见 这篇文章
理解 ThreadLocal 对后面理解 Looper 有很大的帮助,建议先细看 ThreadLocal 的内容再看后面的内容。
Looper 的工作原理
Android 的官方文档中是这么介绍 Looper 的:
Looper 是一个用来为单个线程运行消息循环的类。默认情况下线程是没有一个 Looper 跟他们相关联的。如果一个线程需要 looper 的话,可以通过先调用 prepare() 方法初始化一个本线程的 Looper 实例。然后调用 loop 方法让它开始处理信息,一直到循环结束。
我们通常通过 Handler 类与 Looper 的打交道。
一个 线程最多只能有一个 Looper,一个 Looper 中有一个消息队列(前面 Hanlder 中的 MessageQueue 对象就是从 Looper 中取出的),并且持有它所在线程的引用。Looper 就像一个「死循环」(通过 quit 或者 quitSafely 方法可以退出),它会不断地从 MessageQueue 中查看是否有新消息。如果有,就调用 handler.dispatchMessage 方法进行处理;如果没有,就一直阻塞在那里。
创建 Looper
我们先看看 Looper 的构造方法:
|
|
Looper 构造方法中创建了一个消息队列,并且会保存当前线程对象。
创建普通线程上的 Looper
Looper 的构造方法是私有的,那么要创建一个 Looper。?
调用 Looper.prepare()即可为当前线程创建一个 Looper 对象,接着通过 Looper.loop() 方法开启消息循环。要注意的是:在子线程中,如果手动为其创建了 Looper,那么在所有的任务完成之后,需要调用 quit 方法来终止消息循环,否则这个子线程就会一直处于等待状态。
我们看下 prepare 方法实现
|
|
创建主线程上的 Looper
有一个要注意的地方就是 Looper 的另一个创建方法 —— prepareMainLooper。
prepareMainLooper 在应用的入口方法(ActivityThread.main() )中被调用,用来启动主线程的消息循环。
|
|
|
|
小结
在应用启动时会开启一个主线程(UI 线程),并且开启消息循环,应用不断地从该消息队列中取出、处理消息达到程序运行的结果。
loop 方法
前面所讲都是 Looper 自身的一些特性,没有提到它是怎么跟其他部分交互的。Looper 与其他 MessageQueue 、Hanlder 的交互主要在 loop 方法中。下面我们来看看 loop 方法的源码。
|
|
由源码可见 loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回了 null。
当我们调用 Looper 的 quit 方法时,Looper 会调用 MessageQueue 的 quit 或 quitSafely 方法来通知消息队列退出,否则 loop 方法会一直循环下去。
另外,代码中可以看到,loop 调用了 MessageQueue 的 next 方法来获取新消息,而 next 是一个阻塞操作,当没有消息时,next 方法会一直阻塞在那里,这也导致了 loop 方法一直阻塞在那里。
msg.target.dispatchMessage(msg);// 这里的 msg.target 是发送这条信息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage 方法来处理了。
绕了这么一个大圈意义何在?
通常我们都会在子线程中调用 Handler.sendMessageXxx 或者 Handler.postXxx 方法, 而Handler 的 dispatchMessage 方法是在创建 Handler 的那个线程中执行的,这样就顺利地将代码切换到目标线程中去执行了。
退出 Looper
Looper 也是可以退出的(这里的退出 Looper 主要是指跳出 loop 方法中的死循环)。通过调用 Looper 的 quit 方法或者 quitSafely 方法即可。那么这二者有什么区别?
- 调用 Looper 的 quit 方法可以直接退出Looper。
- 调用 Looper 的 quitSafely 方法只是设定了一个退出标记,然后把消息队列中已有的消息处理完才退出 Looper。
我们来看看这两个方法的实现
|
|
mQueue 的实际类型为 MessageQueue,Looper 的两个 quit 方法都是通过调用 MessageQueue 的 quit 方法来实现的。
|
|
MessageQueue.next() 方法片段。当调用了 quit 方法之后会使得 mQuitting 为 true,从而导致 next 方法返回 null,一旦 next 方法返回 null, Looper.loop 就跳出了死循环。
|
|
MessageQueue.enqueueMessage 方法片段
|
|
从这里实现可以看到,调用了 Looper.quit / quitSafely 方法之后,再通过 Handler 发送的消息无法添加到 MessageQueue 中,此时 Handler 的 send 方法会返回 false。
android.os.MessageQueue#dispose
|
|
回到 Handler
Looper 的 loop 方法中有这样一行代码 msg.target.dispatchMessage(msg);该方法调用的就是 Handler.dispatchMessage 方法。dispatchMessage 方法会根据情况对 Message 进行分发。
Handler.class 代码片段
|
|
dispatchMessage 方法的逻辑:如果 Message 自身设置了 callback,那么事件会直接分派给 msg#callback#run 方法处理。否则,如果给 Handler 设置了 Callback 的话,会先将事件分派给 Callback#handleMessage 方法处理,如果返回值为 true,说明不需要再做进一步的处理,如果返回 false或者是没有给 Handler 设置callback 的 话,则会执行 Handler#handleMessage 方法。
Handler 的 Callback 有什么作用呢?
- 提供了另一种使用 handler 处理消息的方式,在实例化 Handler 时可以使用Callback ,而不是自己实现一个 handler 子类。
- 可以做一些消息过滤。
Handler 小结
Handler 的工作主要包含消息的发送和接收过程(还有一个「分发过程」)。
- 消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现。
- post 系列方法最终是通过 send 的一系列方法来实现的。
查看源码不难发现,Handler 的发送消息的过程仅仅是向 MessageQueue 插入了一条 Message,MessageQueue 的 next 方法就会返回此 Message 给 Looper, Looper 在 loop 方法中对 Message 进行处理,最终由 Looper 交回给 Handler 处理(调用 Handler 的 dispatchMessage 方法)。
数量关系
一个线程最多只能有一个 Looper ,一个 MessageQueue,可以有多个 Handler。
MessageQueue 封装在 Looper 中。
问题
为什么主线程不会因为 Looper.loop()里的死循环卡死?
阻塞是有的,但是不会卡住
主要原因有 2 个:
1.Linux 的 epoll 机制
当没有消息的时候会 epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。
2.所有的 ui 操作都通过 handler 来发消息操作。
比如屏幕刷新 16ms 一个消息,你的各种点击事件,就会有句柄写操作,唤醒上文的 wait 操作,所以不会被卡死了。
一个比较形象的比喻:
就像一辆车在圆形赛车道上跑,一边跑(一边执行任务),比如开到市中心买瓶水,任务完成又回到刚刚离开的地方,继续各种执行买水的任务,直到没有任务了(msg 为空),行了,跑一圈回到起点。睡觉(epoll_wait),有任务叫醒你(唤醒 wait),你又开始跑一圈。边跑边接单。
想进一步了解的同学可以看下知乎上对该问题的讨论
总结
Looper 对象封装了消息队列,Looper 对象被封装在 ThreadLocal 中,是线程私有的,不同线程之间的 Looper 无法共享。Handler 通过与 Looper 之间的绑定来实现与执行线程之间的绑定,handler 发送消息时会将 Message 对象追加到与线程相关的消息队列中,然后由 Looper 回调它的分发消息方法,根据情况处理消息。
最后我们看一张完整的流程图(图片参考自Handler 异步通信机制全面解析),笔者修改了原图中的 Handler dispatchMessage 方法描述。

参考资料与学习资源推荐
- 理解 Java 中的 ThreadLocal
- 谈谈 ThreadLocal
- Android 中 Handler 的使用
- Handler 异步通信机制全面解析
- 深入源码解析 Android 中的 Handler,Message,MessageQueue,Looper
- 探索 Android 大杀器—— Handler
- 《Android 开发艺术探索》
- 《Android 源码设计模式解析与实战》
如果本文中有不正确的结论、说法或者表述不清晰的地方,恳请大家指出,共同探讨,共同进步,谢谢!