解析背景
常见于异步线程持有主线程 handler 对象,借助于 handler 发送 message 回调其 dispatchMessage 方法完成异步通讯 。 再者,Android 源码中大量使用 handler 用于 UI 线程间/线程内通讯。比如 Messener,ViewRootImpl 等等。最近在整理以前源码越苏笔记, 就把整个源码过程都分享下 。
源码解析
涉及核心类
* Looper 类, 负责在 *messageQueue* 中读取 *message* 并调用对应 target (handler) 的处理方法; 一个线程对应一个 *looper*,同时初始化 *messageQueue* * MessageQueue 类,单向链表 * Handler 类,持有 *looper* 对象,可存在多个 *handler* 对象对应一个 *looper* 和一个*messageQueue* * Message 类,持有 target(handler)和 *callback*解析思路
先 “剧透” 一些剧情, 对整个 Handler 体系有很大的帮助: “每一个线程有且仅有一个 *looper* 用于轮询该线程需要处理的消息, 所以也只有一个 *messageQueue* 用于存放消息。 消息可由不同的发送者进行发送, 所以允许有多个 *handler* 发送 *message* 对象到 *messageQueue* 中,但是取出的消息要怎么处理呢?就需要 *message* 得持有 *handler* ,已便取出 *message* 之后可以获取 *handler* 进行分发处理。 所以,按照下面源码阅读有助于理解。 Looper -> MessageQueue -> Handler -> MessageLooper
主要作用是:在线程中初始化 *looper* 对象和 *messageQueue* 对象, 并且保证一个线程有且仅有一个 *looper* 对象; 轮询读取 *messageQueue* 消息。Android 默认提供了 sMainLooper - UI 线程 looper 对象方便任何地方获取。
值得留意:在 Android 8.0 源码中 prepareMainLooper 方法会在 ActivityThread#main 和 SystemServer#run 方法中被调用, 这也说明了 Android UI 线程为何不需要我们主动初始化 looper 的原因 。
1 | //私有静态变量 |
上述代码中,prepare 方法为初始化 looper 对象的入口。
1 | static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); |
sThreadLocal 内部实现为为每个线程保留特定的信息, 这里即为每个线程保留 looper 对象。所以如果已经初始化过 looper 对象,则会异常。
了解的如果初始化之后,剩余的就是如果轮训消息。
1 | //略去不影响主干的部分代码,包括一些判空行为 |
上述的流程精简之后是否干净,就是在代码调用线程获取当前线程 looper 对象, 取出 messageQueue 读取 message 消息, 并调用 Handler#dispatchMessage 进行消息分发 。
值得留意: 既然在 UI 线程循环,那是否意味着会发生阻塞呢? 是的 !但是 UI 线程是 Android 应用的基础,该线程通过 Looper#loop 不断接受事件和分发处理事件, 包括每一个点击, Activity的每个生命周期下的行为等, 如果一旦 UI 线程停止了, 应用也就挂了。 所以只会存在 某一个消息的分发处理会阻塞该循环, 这就意味着不能有耗时操作了。
MessageQueue
既然知道该类是用于存放消息对象, 核心功能在于存取。 整个类有 900+ 行,有部分 native 方法。 这里只看核心的两个方法。 * next 读取消息 * enqueueMessage 存放消息1 | Message next() { |
next 方法主要需要理解以下几点
值得注意的是: 在整个流程中有一个注释为 “遇到消息屏障”(即对应 message.target 是为null ),会循环找出第一个异步消息 ,所有的同步消息将在本次遍历中都会被忽略,直到下次没有消息屏障的时候才取出运行。
- 如果没有异步消息,则直接阻塞
- 如果有异步消息需要处理,先判断时间是否到了,如果没有则需要设置阻塞时间
- 否则把消息返回被设置 mBlocked = false 表示没有阻塞。
那什么时候会出现消息屏障呢 ?
1 | //这里会插入一个消息屏障,在这个屏障之后的所有同步消息都不会被执行,即使时间已经到了也不会执行。 |
那消息屏障的运用有哪些呢 ? 在系统源码 ViewRootImpl 中可见。
1 | void scheduleTraversals() { |
就是为了在处理系统更新视图 UI 时跳过当前队列中所有的同步消息,保证更新任务时最高优先级。
接下来看如何插入消息。
1 | //略去部分代码 |
enqueueMessage 插入代码指定了一个 message 按照 when 规则插入消息队列。
- 如果当前队列为空或者when为0(立刻执行)或者when比头部链表节点when小,则插在头部
- 其他场景下按照 when 从小到大规则查到相邻节点(when比他大),插在它前面。
Message
如果仔细看了前两节,相信对整个存取过程会有清晰的理解。 接下来看一下在 *messageQueue* 中存储的消息 *message* 是怎样的存在。1 | //支持跨进程层/intent传递 |
上述是绝大部分成员变量,主要涉及定义 message 的状态及获取回收 message 时缓存的处理。
1 | //所有获取方法最后的入口 |
可见缓存池实际上是一个 message 链表,sPool 为链表头部。
1 | void recycleUnchecked() { |
获取和回收流程比较简单, 看这里细心的读者可能有个疑惑 “那 message 的 flags 何时才会设置 FLAG_ASYNCHRONOUS” 呢 ?
1 | int flags; |
这里的同步异步可能和我们理解线程同异步有所偏差。
实际上无论是同步消息还是异步消息,都是放到 messageQueue 中按照 when 规则进行获取分发 。但是当有一些 “紧急事务” 需要在队列中优先被执行,则可以通过 “同步屏障” 来过滤掉原来本在排队执行的同步消息,直接运行最接近运行时间的异步任务 。上述 ViewRootImpl 绘制就是一个很好的例子 。
Handler
1 | final Looper mLooper; |
1 | public void dispatchMessage(Message msg) { |
其内部允许设置 mCallback 拦截 handler 的处理逻辑,另外如果一个 message 携带有 callback ,则直接调用 Callback#run 完成分发处理 。
上述代码为处理消息,以下代码为发送消息。
1 | //发送代码会调用 |
也就是说,handler 决定了发送的 message 是否为异步消息 。
至此,整个 Handler 体系解析完毕 。
如果错误,请不吝指出 !