上一篇,我们了解了 Java 中,我们分析了下 Handler 机制,了解了 Handler Looper MessageQuene 之间的关系,但是对于 Handler 还有一部分要说的。一个消息类型 和 idleHandler。
消息类型
Handler的Message种类分为3种:
- 普通消息
- 屏障消息
- 异步消息
其中普通消息又称为同步消息,屏障消息又称为同步屏障。
我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
其实对于开发者,异步消息是不暴露使用的。只是对于系统的层面的 API 使用,比较常见的一个例子是 ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
通常情况来说,二者使用行为没有差异,只有在设置同步屏障之后,才会有异步消息优先执行的效果。就是简单的设置一个执行的优先级。
屏障消息
消息的创建是在 MessageQueue 中,设置实现的:
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
屏障消息没有 target 的设置。这也是为啥上一篇中,我们再说 MessageQuene.next 方法中时,有 target 等于 null 的判断。
而且屏障消息入队是不需要唤醒队列的,因为他其实只是一个过滤作用,不会真正的传递什么 message。
postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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) {
// 如果当前的消息是 消息屏障,那么会返回最近的一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
...
}
...
}
}
IdleHandler
通过名字我们可以知道,他其实是一个 “空闲Handler”,既在 Handler 空闲的时候做些什么事情,但是不能保证时机。
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
简单的来说就是,这个 Idle 执行完了之后,要不要从当前的执行队列中清除,返回false就是清除,反之亦然。
之前我们看到 Message.next 方法,里面有如下部分.
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 省略消息队列有数据的情况。
...
// 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;
}
}
当消息队列有效数据为空的时候,idleHandler 会被调用到,其实也就是在 nextPollTimeoutMillis = -1; 赋值的时候,这时候不会有任何的 msg 返回,因此会执行到 mIdleHandlers 的相关代码。
处理完IdleHandler后会将nextPollTimeoutMillis设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的message执行。
如果一个 IdleHandler 返回了 true 为什么不会造成死循环?
这其实是由于 pendingIdleHandlerCount 的赋值时机,第一进入 next 方法时,会将 pendingIdleHandlerCount 赋值为 -1,如果消息队列为空或者消息执行时间还不到,那么会执行 pendingIdleHandlerCount 初始化的内容。
当执行完一次 pendingIdleHandlerCount 赋值为 0,同时 nextPollTimeoutMillis 赋值为0。同时由于还在for 循环中,尝试获取第二次的队列中的消息,因为 nextPollTimeoutMillis == 0 相当于非挂起等待,直接尝试获取。如果有了Messgae会直接返回,没有的话继续往后执行,但是这一次 pendingIdleHandlerCount == 0,因此会直接开启第三次循环,但这时候 nextPollTimeoutMillis == -1或者下次唤醒间隔。
唤醒之后,必然会有一个相应的 Message 会返回,最终也不再会走到 IdleHandler 的相关内容了。所以这也就是为啥只有第一次空message时会走到 IdleHandler 的处理逻辑,后面都不会了。