面试再也不怕 Handler 了,消息传递机制全解析
Posted 涂程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试再也不怕 Handler 了,消息传递机制全解析相关的知识,希望对你有一定的参考价值。
一、为什么要使用 Handler
众所周知,android 不允许在子线程中更新 UI。但是我们在子线程完成耗时的操作之后,需要对界面数据进行更新,又该怎么处理呢?这时候,我们可以使用 Handler 进行 UI 更新。值得注意的是,更新 UI 我们需要把 Message 发送到主线程持有的 MessageQueue ,否则程序依然就会发生奔溃。
另外,除了更新 UI,Handler 是 Android 系统的消息传递机制,它定义了一套处理消息的规则,广播、服务以及线程间的通信都需要靠它来完成。
与 Handler
相关的还有 Looper
和 MessageQueue
,接下来我们就从它的使用开始分析,对这三剑客一网打尽。
二、Handler 发送消息的流程
Handler 发送消息有两种方式,一种是 sendMessage
的方式,一种是 post
的方式,通过对源码的阅读,post 的方式其实是调用到了 sendMessage 的方式。那我们就来看看 sendMessage 的流程吧。通过调用 sendMessage,最终会走到下面方法中:
这里做的事情很简单,必须满足 MessageQueue 不能为空,否则程序会抛出异常,接下来看 enqueueMessage 的流程:
在这里完成了两个重要的流程:
- 为 msg 的 target 赋值,msg.target = this,
因此这个 target 就是调用的 sendMessage 的 Handler。
(记住这里的重点) - 调用了 MessageQueue 的 enqueueMessage 方法。
到目前为止,流程来到了 MessageQueue 中。现在看 MessageQueue 的 enqueueMessage 方法。
三、MessageQueue 的工作流程
由于 enqueueMessage 的方法比较长,我们这里不截图,直接看下面的代码:(省略部分代码)
boolean enqueueMessage(Message msg, long when) {
// 1、target 不能为空,否则直接抛出异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 2、加锁,不能有多个 Handler 同时发送消息
synchronized (this) {
msg.when = when;
Message p = mMessages; // 出队列的 msg 的下一个要出队列的 msg
boolean needWake;
// 3、下面这三种情况直接插在 head 节点上,(1)这个队列是一个空队列,
// (2)这个 msg 需要立即处理,(3)是它需要处理的时间比即将出队列的节
// 点的处理时间还要小
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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
// 4、如果之前第三点的条件不满足,就会从 head 节点开始遍历,
// 插入到一个合适的时间,或者链表的尾部,这个 for 循环做的其实就是
// 链表节点的插入
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;
}
// 5、是否需要进行唤醒,在 queue.next() 方法中如果没有获取到 msg就会休眠
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
解释其实已经在上面的代码里了,下面来做一个简单归纳:
MessageQueue 其本质上一个单向链表,
入队列这个操作进行了加锁的处理,不能多个 msg 同时入队列。- 在插入队列的时候,会根据当前队列是否为空,或者处理消息的时间选择合适的插入位置。
- 最后判断是否需要进行 wake up
到目前为止,我们看了 Handler 的发送消息的流程,以及消息是如何插入链表的,那么消息是如何处理的呢?我们知道,只有调用了 Looper 的 loop() 方法之后,才能处理消息,那接下来看 Looper 的 loop() 方法。
四、Looper 的工作流程
Looper 的 loop() 方法也是相当长,接下来看代码:(省略部分代码)
public static void loop() {
// 1、获取 Looper 对象,定进行判空处理
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 2、获取了 MessageQueue 对象
final MessageQueue queue = me.mQueue;
for (;;) {
// 3、调用 MessageQueue 的 next(),返回值是 msg
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
....
try {
// 4、之前说过,在 SendMessage 的时候设置了 msg 的target,这个 target 就是调用 sendMessage 的 Handler
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
} finally {
}
msg.recycleUnchecked();
}
}
代码本身很长,但是其实做的事情也不多,现在简单归纳一下:
- 在调用 Looper.loop() 之前,必须先调用 Looper.prepare(),如果没有
Looper 对象的话程序会直接抛异常。
- 通过调用 MessageQueue 的 next 方法不断的从队列里取消息出来。
- 最后把 msg 交给 Handler 的 dispatchMessage() 进行处理。
通过源码我们可以发现调用 queue.next() 时可能发生阻塞,那这个方法又做了什么?还有,为什么要先调用 Looper.prepare(),这个方法又做了什么处理?先来看比较简单的吧:
这个 Looper.prepare() 其实是创建了一个 Looper 对象,并且通过 ThreadLocal 实现每个线程有且仅有一个这样的 Looper 对象。为什么要创建 Looper 呢?没有就不行吗?我们来看 Handler 的构造函数:
可以看到,如果 Looper 为空的话,程序直接抛异常。这个 myLooper() 是用来获取当前线程的 Looper 对象:
从时序上说,我们调用 Looper.prepare() 的时机必须在 new Handler() 之前。
那么,我们主线程使用 Handler 的时候,并没有调用 Looper.prepare() 这个方法,这又是怎么回事呢?
原来,在 ActivityThread 的 main() 方法中已经为我们进行了处理:
这个 prepareMainLooper() 在内部调用了 Looper.prepare() 。到目前为止,我们解决了 Looper 的相关问题,说明了必须存在 Looper 的原因。现在还有一个问题没有解决,queue.next() 方法做了什么事情?它为什么发生阻塞呢?
接下来看 MessageQueue 的 next() 方法:(已省略部分代码)
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 1、这是一个 native 方法,如果messageQueue 没有可以处理的消息就会休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 2、同步屏障,寻找队列中的下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 3、下一个出队列的这个 msg 还没有到时间,并计算需要阻塞的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 4、得到一个能够处理的msg,并返回这个 msg
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
}
}
重要的点其实已经在上面说了,下面总结一下:
- 获取 msg 的这个过程有可能会发生阻塞,具体调用到的是 native 的 nativePollOnce 方法
- 获取消息的时候,有一个同步屏障,也就是对 msg 对应的 target(Handler) 为空的消息进行了过滤。
- 如果能获取到一个 msg ,那么就返回这个 msg。
四、再看 Handler
先来梳理一下我们现在明白了什么:
- 在创建 Handler 的时候,必须先创建 Looper 对象,之后还需要调用 Looper.loop() 方法才能让 Handler 开始工作。
- 通过 Handler sendMessage 发送消息,其实是调用了 queue.enqueueMessage,这个 Queue 其实是一个单向链表,在调用这个方法的时候,会根据当前队列的转态以及 when 把这个 msg 插入到合适的位置。
- queue.next() 可能会发生休眠,原因是拿到不到合适的 msg,在 queue.enqueueMessgae 的时候会判断是否需要唤醒。
之前我们说过,这个 msg 其实是交给了 Handler 的 dispatchMessage 去处理,下面来看一下 Handler 是怎么处理的:
msg.callback
是我们通过 post 方法传递进来的一个 Runnable 对象,如果我们没有使用 post 的话,就不会走到handleCallback(msg)
中。- mCallback 是一个 CallBack 对象,如果我们在创建 Handler 的时候没有传这个参数,那么 mCallback 也是为null 的。
- 最后才会走到 handleMessage(msg) 中。
为方便学习了解到更多的Handler知识点,特此我将一些 Android 开发相关的学习文档、面试题、Android 核心笔记等文档进行了整理,并上传之我GitHub项目中,如有需要参考的可以直接去我 GitHub ,希望能帮助到大家学习提升。
以上是关于面试再也不怕 Handler 了,消息传递机制全解析的主要内容,如果未能解决你的问题,请参考以下文章