如果把App比作人体,那么Handler机制就是人体的整个神经系统。它维系着从脑部到全身的任何一个部位的命令信息的传递。
Handler 机制的运作方式
Handler机制正常运作离不开基本的三要素:Handler、Looper、MessageQueue。(如果把Message算作第四要素也没问题)。
Looper 是整个机制的核心,可以看做是消息引擎,当Looper在运转的过程中,其自身机制会保证从MessageQueue(消息队列)中不断地获取消息(Message)并执行。
MessageQueue 是承载Message的容器,其特性属于队列性质,但存在一些特殊操作,这一点我们后面再说。
最后就是Handler,它提供了管理Looper,调用Looper,和外部组件交互的能力。
Handler的源码并不多,功能也并不复杂,下面重点讲解Handler的重要方法。
- 初始化。
Handler提供了多个构造函数,但最终都会汇总到下面两个构造函数:
public Handler(Looper looper, Callback callback, boolean async)
和
public Handler(Callback callback, boolean async)
构造方法参数不多,looper是消息引擎,在与外界交互的时候毕传;
callback是外界自定义回调,若callback不为空,则不再走Handler原生的handlerMessage
方法而是调用callback,代码逻辑如下。
执行的优先级排序从大到小依次为:message.callback,handler.callback,handler.handlerMessage()。所以当调用场景命中了所有以上三个回调时,他们的优先级要仔细分辨。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
第三个参数:async,是否异步。一般情况下,我们在Handler做手动初始化时,都不需要传这个参数,是因为在调用Handler(looper,callback)的时候,已经默认讲async赋值了false。那么这个值是什么意思呢?看下一段讲解。
三种消息类型和同步屏障
在handler 入队消息时,会检查mAsynchronous,也就是async的值,如果是true,则设置该msg为异步执行。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
设置msg为异步执行有合用途呢?对MessageQueue中的消息执行会产生什么样的影响呢?我们接下来看MessageQueue中的方法:next().
Message next() {
...
for (;;) {
...
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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
}
这里需要理解两个重要概念:1. Message的分类;2. AsyncBarrier机制。
跟聚用途和调用机制的不同,Message大体可分为三类:1. 普通Message;2. 异步Message;3. 同步屏障Message。
首先是Message的执行机制:也就是从Handler.post()开始到最终handler.dispatchMessage(),是如何实现的。
以下代码描述了Handler.post()和Handler.postDelay()的机制。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
经过层层调用,post/postDelayed -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage,最终追到queue.enqueueMessage(msg, uptimeMillis)
方法中。
boolean enqueueMessage(Message msg, long when) {
。。。
synchronized (this) {
。。。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
。。。
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
else中的语句说的其实是链表的按序插入。插入标准就是Message中的when变量。从之前的调用链一路走来会发现,when的值就是该Message所应该执行的精确的时间点。根据这个时间点,在message入队的时候进行插入,确保队列顶部的message先于后续message执行。如果调用的是postDelayed方法,则 “when = 当前时间 + delay时间”。这也就是post和postDelay的实现原理。到目前为止,我们只讲了一半,就是Message是如何入队的。那么通过MessageQueue.next()执行的过程中,轮到该Message出队并执行时,又是如何?下面再次引用MessageQueue的next()方法源码:
Message next() {
。。。
for (;;) {
。。。
synchronized (this) {
// Try to retrieve the next message. Return if found.
//获取一个队列头部的msg
。。。
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//如果该Message的when的时刻还没有到,则设置一个唤醒时间,到时候再执行。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//否则出队,返回该Message对象。同时对其进行标记,“正在使用”。
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;
}
。。。
}
。。。
}
}
出队的时候,首先找到一个队列头部的Message,判断其when字段的执行时刻是否已经到来,如果没有,则设置一个“唤醒时间”,到时候再执行。否则,对队列执行出队操作,重设链表指针,并返回该Message。
通常情况下我们对MessageQueue的使用也就到此了。但Handler构造中对async 的设置,使得按顺序执行的规则并不是一成不变的,Handler机制为“异步Message”提供了“超车道”。这使得对应的Handler在初始化时,其消息就是异步执行的。
结合上面这段MessageQueue.next()代码,就会产生一个问题:既然Handler在初始化的时候就决定了其消息是异步还是同步执行,为什么还要在next中区分消息是否是异步还是同步呢?
这里存在一个误区,同一个线程,是否可以有多个Handler运行?答案是肯定的,可以有多个。但是,同一个线程能否有多个Looper呢?答案是否定的,一个线程最多只能有一个Looper,也就是最多只能有一个引擎,这意味着只能有一个MessageQueue运转(MessageQueue是Looper的成员变量,在Looper的prepare阶段准备好)。
我们分别看一下Handler的某一个构造函数和Looper的prepare()方法就知道了。
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler的这个构造方法要求我们必须在构造它之前在当前线程存在一个Looper,mLooper = Looper.myLooper()
,若没有,则运行时异常,报错为“当前线程没有Looper存在”。上述逻辑解答了为什么MessageQueue.next()中会区分对待Message的异步和同步类型:因为他们是可能存在于同一个MessageQueueu中的,而这个取决于当前线程是否存在不同async值的Handler()对象。
下面就自然产生了如下两个问题:1. 异步Message的用途是什么,为什么MessageQueue要给这个另类提供超车道?2. 既然AsyncMessage只是相对于普通Message设置了async标记,除此外在入队的过程中没有其他的任何不同,那么MessageQueue是通过什么方式或者机制支持异步消息执行的呢?
先回答第二个问题:MessageQueue对于“异步消息”是如何支持的。这里就要引入我们的第三类Message了————同步屏障(SyncBarrier)。
同步屏障机制能够有效保证异步消息的正常执行。由于三种消息均处于同一个MessageQueue中,因此当同步屏障被按规则插入之后,Looper在执行next方法时若执行到一个同步屏障,则直接寻找后续队列中遇到的第一个异步Message,并拿出来执行(详见MessageQueue.next())。
一下是上文对next代码中省略的一部分,专门用来处理同步屏障的逻辑:
Message next() {
。。。
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.
// 遇到了同步屏障,则开始遍历后续Message,寻找异步消息类型并取出。
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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
现在解答第一个问题:为什么Handler机制要提供异步消息的类型?用途是什么。
一个最典型的应用,发生在Android View的绘制过程中(此处应该有独立知识点),为了保证在渲染信号到来的时候,刷新机制能够立刻执行,使用到了该机制。在ViewRootImpl中存在如下方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
同时当view绘制完毕的时候,必须调用如下方法消除同步屏障,否则该屏障消息会始终存在于MessageQueue中:
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
这也刚好印证了,MessageQueue中为何会存在removeSyncBarrier()方法。因为next方法中不涉及到对同步屏障的清楚处理。需要手动调用。
总结
- Handler机制三要素:Handler、Looper、MessageQueue(包含Message)
- 一个线程最多只能有一个Looper和一个消息队列,但可以有多个Handler。
- Handler的post、postDelay等方法的实现原理,when字段在整个机制中的重要意义。
- MessageQueue.next方法的运行方式。
- 同步屏障的概念和实现原理,用途。
关联问题:
如果SyncBarrier的插入晚于某个异步消息,怎么办。有没有机制能够保证SyncBarrier一定在该异步消息之前插入?
postSyncBarrier(long when)这个方法就是最好的保证。无论是屏障消息还是异步消息,都需要遵循由when决定的插入顺序,因此postSyncBarrier的时机只要发生在目标异步的消息的when之前即可。. 消息机制是如何切换线程的?比如从子线程切换到主线程。
其实就是拿到了对应主线程的Handler。最常用的方式就是Activity.runOnUiThread()。首先判断当前线程是不是ui线程,如果是,则直接调用action.run()。否则,获取主线程Handler进行post。
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
通过这个例子可以明白,无论我们处于哪个线程,只要有办法获取目标线程所绑定的Handler,即可实现消息的线程切换。
ANR监听的是什么?为什么Looper在执行死循环的 时候不会触发ANR。
ANR的监听是一个比较大的主题,简单来说,ANR监听的并不是Looper的循环情况,而是每个Message在执行过程中的执行时间,因此题目其实是具备误导性的。四大组件中对消息的执行时间的要求也是不一样的。-
ThreadLocal是什么?有什么作用?
ThreadLocal并不是Thread,它其实是是一个容器,专门存放跟Thread相对应的内容。在Handler机制中,ThreadLocal存放 的是跟线程一一对应的Looper对象。若在实例化Handler的时候Looper还没有绑定到该线程,则抛出异常。Looper通过prepare进行初始化,存入threadLocal。