说到Handler的消息机制,相信大家谈起这个的时候,多多少少都会有所了解,甚至会说到,还比较熟悉吧!那笔者也自信一把,算是比较熟悉!!!
但是笔者在跟踪学习View的invalidate()、requestLayout()方法的源码时,总是会遇到这样一段不甚知晓的代码:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 关注点 1.通过MessageQueue#postSyncBarrier()设置Handler消息的同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 关注点 2.Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调
// 有了同步屏障mTraversalRunnable就会被优先执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
关注点 1:
通过MessageQueue#postSyncBarrier() 设置Handler消息的同步屏障,有了同步屏障mTraversalRunnable就会被优先执行;
问题 1: 那么问题来了,什么是同步屏障呢?
关注点 2:
通过Choreographer#postCallback() 提交一个任务,mTraversalRunnable是要执行的回调任务。
问题 2: Choreographer#postCallback()只是提交一个任务吗?为何它提交一个任务,上面设置了同步屏障,它提交的任务会被优先执行?
问题 1:
首先来看一下什么是同步屏障?
同步屏障是 MeesageQueue 中一种特殊的机制,源码注释如下:
/**
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*/
英文注释的大概意思是: 在消息队列中遇到已经post进来的同步障碍之前,消息的处理是正常进行的。当遇到同步屏障时,消息队列中位于同步屏障后的同步消息将被暂停(阻止执行),直到通过调用 MessageQueue#removeSyncBarrier 并指定标识同步屏障的token来释放同步屏障,以确保消息队列恢复正常操作。否则,应用程序可能会挂起。
消息队列中的异步消息不受同步障碍的限制,可以继续像正常情况一样进行循环遍历并处理。
下面就来看一下消息队列MessageQueue#postSyncBarrier() 方法,追踪查看是如何插入同步屏障的。
MessageQueue#postSyncBarrier():
public int postSyncBarrier() {
// 传入系统的当前时间,调用有参的postSyncBarrier()
return postSyncBarrier(SystemClock.uptimeMillis());
}
继续调用 MessageQueue#postSyncBarrier(long when) 插入一个同步屏障,when 参数指定屏障插入的时间。
MessageQueue#postSyncBarrier(long when):
private int postSyncBarrier(long when) {
// 加入一个新的同步屏障token
// 我们无需唤醒消息队列,因为同步屏障的目的就是为了暂停消息队列的
// 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;
}
}
该方法中,首先构建了一个Message对象并插入到了消息链表中。乍一看好像没什么特别的,但是这里面有一个很大的不同点是该Message是没有给 target 赋值的。也就是说同步屏障是一个特殊的 Message,它没有对应的 Handler,它的插入逻辑和普通消息的插入是一样,但是这个插入不会唤醒 block 住的 poll 循环。
所以,从代码层面上来讲,同步屏障就是一个Message,一个target字段为空的Message。
而通常情况下,我们使用Handler向任务队列添加Message,Handler中发送消息的方法有post***、sendEmptyMessage***以及sendMessage***等,而这些最终都会调用enqueueMessage()方法。
Handler#enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis):
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// msg的target赋值为当前发送消息的Handler自身
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 如果是异步消息,Message通过调用setAsynchronous(true)方法,将该消息标记位异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
通过这个方法我们可以看到,由enqueueMessage()插入到消息队列的消息,消息msg的 target 字段都赋值为this,也就是发送该消息的Handler本身。
同步屏障插入到消息队列之后,我们知道消息队列是由Looper管理的,在Looper#loop() 方法中,开启一个死循环,不断的遍历消息队列。
Looper#loop():
public static void loop() {
final Looper me = myLooper();
......省略代码
// 获取 Looper 的 MessageQueue 对象
final MessageQueue queue = me.mQueue;
......省略代码
// 开启死循环,不断的遍历消息队列
for (;;) {
// 不断的调用 MessageQueue 的 next()方法返回一个待处理消息
Message msg = queue.next(); // might block
......省略代码
try {
// msg.target指的就是发送该消息的Handler,所以调用的是Handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
......省略代码
} catch (Exception exception) {
......省略代码
} finally {
......省略代码
}
......省略代码
// 回收消息
msg.recycleUnchecked();
}
}
Looper#loop() 方法只是开启消息队列的循环,要查看消息队列内部是如何取出一个待处理消息的,查看MessageQueue#next()方法。
MessageQueue#next():
Message next() {
......省略代码
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 该方法会持续 block,直到超时或者被 nativeWake 方法唤醒
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;
// 这里是处理同步屏障的,前面我们分析过,同步屏障是一个特殊的消息,它的target是空的
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 如果当前队首消息是同步屏障消息(target==null),那么就循环寻找下一条异步消息并取出队列
// while 的判断条件,消息不为null,并且不是异步消息时,继续循环寻找下一条消息
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 继续调用 nativePollOnce 等待
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;
}
......省略代码
}
......省略代码
nextPollTimeoutMillis = 0;
}
}
由此可见,同步屏障和异步消息通常是搭配使用的。看到这里,同步屏障是什么?童鞋们应该清楚了吧!
接下来针对 问题 2,可以大胆猜测一下,Choreographer 通过 postCallback 提交一个任务,那么是不是这个任务里面创建并发送了一个异步消息呢?
Choreographer是负责获取Vsync同步信号并控制App线程(主线程)完成图像绘制的类。
// 这个任务里面创建并发送了一个异步消息呢?
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
问题 2: Choreographer#postCallback()只是提交一个任务吗?为何它提交一个任务,上面设置了同步屏障,它提交的任务会被优先执行?
还是从源码中找答案,跟踪查看
Choreographer#postCallback() 方法:
/**
* Posts a callback to run on the next frame.
* 将回调放在下一帧调用
*/
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
该方法的注释说的很明白,将传进来的回调放到下一帧调用,该方法内部又调用了
Choreographer#postCallbackDelayed() 方法:
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
......省略代码
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
方法内部又调用了
Choreographer#postCallbackDelayedInternal() 方法:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
......省略代码
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 将消息以当前的时间戳放进mCallbackQueue 队列里
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 如果没有设置消息延时,直接执行
scheduleFrameLocked(now);
} else {
// 有消息延时,但是最终依然会调用scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
将回调任务加入队列mCallbackQueues中,由于传入的delayMillis为0,所以接下来执行
Choreographer#scheduleFrameLocked() 方法:
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
......省略代码
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
// 如果当前线程是Choreographer的工作线程,我觉得就是主线程
scheduleVsyncLocked();
} else {
// 否则发一条消息到主线程,并设置为异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
// 插到消息队列头部,这里可以理解为设置最高优先级
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
如果运行在Looper线程上,则立即调度vsync;否则,通过Handler发一个异步消息到消息队列,最终也是到主线程处理,这个消息是异步消息,在先前同步屏障的作用下,会优先执行。那么接下来进入
Choreographer#scheduleVsyncLocked() 方法:
@UnsupportedAppUsage
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
继续进入
DisplayEventReceiver#scheduleVsync():
/**
* Schedules a single vertical sync pulse to be delivered when the next display frame begins.
* 当下一个显示帧开始时,调度并传递一个单独的垂直同步脉冲。
*/
@UnsupportedAppUsage
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
// 调用JNI方法nativeScheduleVsync请求注册vsync信号
nativeScheduleVsync(mReceiverPtr);
}
}
nativeScheduleVsync() 是通过JNI调用Native层的代码,它会向SurfaceFlinger注册Vsync信号的监听,VSync信号由SurfaceFlinger实现并定时发送,当Vsync信号到来的时候就会回调DisplayEventReceiver#dispatchVsync()
DisplayEventReceiver#dispatchVsync():
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
在Choreographer中,mDisplayEventReceiver是FrameDisplayEventReceiver,且继承自DisplayEventReceiver,并重写了onVsync() 方法,所以下面需要进入到
FrameDisplayEventReceiver#onVsync():
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......省略代码
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
......省略代码
mTimestampNanos = timestampNanos;
mFrame = frame;
// 构建一个消息,并携带一个回调任务,也就是FrameDisplayEventReceiver
Message msg = Message.obtain(mHandler, this);
// 设置该消息为异步消息
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
FrameDisplayEventReceiver#onVsync() 方法,在特定的时刻发送消息,Message携带回调Runnable消息,而FrameDisplayEventReceiver实现Runnable,因此,最后在特定的时刻运行FrameDisplayEventReceiver的run方法,进而调用了
FrameDisplayEventReceiver#doFrame():
@UnsupportedAppUsage
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
......省略代码
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
......省略代码
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
......省略代码
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 恢复mFrameScheduled标志
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
// 这个就是我们要找到的执行回调
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......省略代码
}
该方法会计算当前时间与时间戳的间隔,其中入参代表上一个帧的时间,如果当前时间已经>=frameTimeNanos一个间隔,表示发生了跳帧,根据时间差值/帧间隔计算跳过帧数,重置frameTimeNanos为不发生挑帧时,最近的一个帧时间点。最后,设置mLastFrameTimeNanos值,记录上一帧的时间。
紧接着就是回调处理每个类型的Callbacks,触发CallbackRecord的run方法,CallbackRecord封装了任务action。对于CALLBACK_TRAVERSAL类型,最终会回调到 ViewRootImpl#TraversalRunnable 的run方法。
FrameDisplayEventReceiver#doCallbacks():
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 设置同步屏障后,我们post进来一个任务消息,并加入到mCallbackQueues,这里需要根据类型和时间戳取出来
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
......省略代码
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
// 从 mCallbackQueue 中取出CallbackRecord并按照时间戳顺序调用 mTraversalRunnable 的 run 方法
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
首先根据类型 callbackType 和时间戳取出来对应的CallbackRecord,CallbackRecord和链表的节点值定义相似,里面封装了任务action、下一个CallbackRecord的指向next等。然后按照时间戳顺序调用 mTraversalRunnable 的 run 方法,对于CALLBACK_TRAVERSAL类型,最终会回调到 ViewRootImpl#TraversalRunnable 的run方法。
ViewRootImpl#TraversalRunnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 通过调用 removeSyncBarrier 方法移除同步屏障,传入 postSyncBarrier 返回的 token 作为参数
// 标识需要移除哪个屏障,然后将该屏障消息会从队列中移除,以确保消息队列恢复正常操作,否则,应用程序可能会挂起
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 到这里就是我们所有与屏幕刷新有关的源码分析的入口啦!
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
贴一张FrameDisplayEventReceiver的onVsync方法执行流程图,便于理解。
分析了这么多,结合问题 1 和问题 2 总结一下:
我们知道,Android是基于消息机制的,每一个操作都是一个Message,如果我们想要主动去触发绘制的时候,消息队列中还有很多消息(同步或异步消息)没有被执行,那是不是意味着需要等到消息队列中的消息执行完成后,绘制消息才能被执行到呢?
有人可能会说,直接将Message的执行时间设置为当前时间就行了,当然可以,但是如果队列中有多个类似的Message,就不能保证你的Message能够在下次执行(就算是高优的任务,也不能打断正在执行的任务,所以只能是下次)。
针对这个情况,在查看源码时,我们发现系统源码在 Choreographer#scheduleFrameLocked 和 FrameDisplayEventReceiver#onVsync 中, 会把与绘制请求有关的Message设置成异步消息(msg.setAsynchronous(true)),再结合 MessageQueue#postSyncBarrier ,能够把我们的异步消息(也就是绘制消息)的优先级提到最高。
主线程的 Looper 在一直循环调用 MessageQueue 的 next 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next 方法陷入阻塞状态。
如果 next 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。
所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除,否则主线程就一直不会去处理同步屏障后面的同步消息,应用也将挂起;