(源码版本:android-29)
为了更好地理解Choreographer,建议先读看下这篇文章:Android的16ms和垂直同步以及三重缓存
说道View的绘制流程,平时用的最多的就是View的onMeasure、onLayout、onDraw三组相关的方法,都2021年了,行业这么卷,相信大家都用的滚瓜烂熟了。让我们深入一点。
debug的时候,在这些方法中打断点可以看到在调用栈中,是ViewRootImpl触发了这些方法。
ViewRootImpl中,持有了View的成员mView,ViewRootImpl对View的直接调用是通过以下方法:
measure方法在performMeasure中:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
而performMeasure方法被measureHierarchy、performTraversal两个方法调用。
layout方法在performLayout方法中被调用:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// 省略一些代码
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 省略一些代码
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
可以看到View的layout方法在两个地方被调用,而performLayout方法被performTraversal方法调用。
draw方法在drawSoftware方法中:
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
查找drawSoftware方法的引用,发现调用顺序为:performDraw->draw->drawSoftware。
小结:
三个方法都被performTraversal方法调用,可以看出,performTraversal是View绘制的入口方法。
measure除了被入口方法调用外,还被measureHierarchy方法调用,查看measureHierarchy方法的源码,发现有三处逻辑调用,所以,视图的测量是会进行多次的。
继续,performTraversal被doTraversal调用:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
再然后,doTraversal封装成了一个Runnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
VIewRootImpl有一个TraversalRunnable成员:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
接下来是绘制和取消绘制的方法:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
我们搜下开始绘制流程方法的引用,有18个,看来很多参数的改变都会是ViewRootImpl开始进行绘制:
我们先歇一会,不往scheduleTraversals的调用者方向撸了,换一个方向,我们注意到如下图:
诶?我们的mTraversalRunnable是我们主要的要执行的任务,上图红框里的是什么东西,为什么要交给它,它是干什么的?
引入今天的主角Choreographer,也是挺有名的,大伙应该经常能听到他的名字,如果说精通View的绘制流程,但如果你不认识他,那真是说不过去。
首先,Choreographer是作为一个成员变量被ViewRootImpl持有:
Choreographer mChoreographer;
我们再往前撸Choreographer的调用:
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
然后:
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
再然后:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
上图先把这个action(就是我们之前的绘制Runnable)放到了队列mCallbackQueues里,然后出现了两个分支。
分支a1:如果预期执行的实现早于现在,则执行scheduleFrameLocked方法
分支a2:否则发送一个消息给mHandler。
我们先不管分支a1,先看下mHandler是什么呢:
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
mHandler是FrameHandler对象,FrameHandler是Choreographer的内部类,处理了3个消息。
又有分支了,有点懵,没关系,看名字:
- MSG_DO_SCHEDULE_VSYNC应该是和垂直同步有关,直接调用了doScheduleVsync方法,我们先不管,记作分支b1。
- MSG_DO_FRAME,一看就是我们要的真正去处理逻辑的方法,也先不管,记作分支b2。
- MSG_DO_SCHEDULE_CALLBACK,上面传的就是这个,记作分支b3,先看这个:
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
上图也是调用了scheduleFrameLocked方法(前面的分支a1也是调用这个方法)。
scheduleFrameLocked是干嘛的呢:
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on 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()) {
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);
}
}
}
看上图的分支:
如果没有启动垂直同步,直接发送MSG_DO_FRAME消息到FrameHandler,然后会调用doFrame方法。
对于开启了垂直同步的情况,如果是在主线程,直接调用scheduleVsyncLocked方法,会向一个FrameDisplayEventReceiver对象发送垂直同步消息;
对于开启了垂直同步的情况,如果不在主线程,向FrameHandler发送MSG_DO_SCHEDULE_VSYNC消息,在Handler中会调用doScheduleVsync方法,这就到了前文的b1分支,直接调用doScheduleVsync方法,看doScheduleVsync方法,还是调用的scheduleVsyncLocked方法,只不过加了个锁判断了个状态:
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
scheduleVsyncLocked方法通过FrameDisplayEventReceiver最终还是调用了doFrame方法:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
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;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
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);
}
}
最后我们看下doFrame方法:
@UnsupportedAppUsage
void doFrame(long frameTimeNanos, int frame) {
// 省略成吨的代码...
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);
}
// 省略一些代码
}
这就是最终boss了。
回顾之前ViewRootImpl,mTraversalRunnable是所有绘制的入口,这个Runnable刚刚被postCallbackDelayedInternal方法放在了队列数组里面。
我们再看看数组:
private final CallbackQueue[] mCallbackQueues;
private final class CallbackQueue {
private CallbackRecord mHead;
// ...
}
这个数组包含了一个CallbackRecord成员:
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
这个类有dueTime(预期的执行时间),有action(Runnable),还有一个next,是一个单项链表。
再看mCallbackQueues是用什么作为数组索引呢?
/**
* Must be kept in sync with CALLBACK_* ints below, used to index into this array.
* @hide
*/
private static final String[] CALLBACK_TRACE_TITLES = {
"input", "animation", "insets_animation", "traversal", "commit"
};
/**
* Callback type: Input callback. Runs first.
* @hide
*/
public static final int CALLBACK_INPUT = 0;
/**
* Callback type: Animation callback. Runs before {@link #CALLBACK_INSETS_ANIMATION}.
* @hide
*/
@TestApi
public static final int CALLBACK_ANIMATION = 1;
/**
* Callback type: Animation callback to handle inset updates. This is separate from
* {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
* {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
* update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
* that contains all the combined updated insets.
* <p>
* Both input and animation may change insets, so we need to run this after these callbacks, but
* before traversals.
* <p>
* Runs before traversals.
* @hide
*/
public static final int CALLBACK_INSETS_ANIMATION = 2;
/**
* Callback type: Traversal callback. Handles layout and draw. Runs
* after all other asynchronous messages have been handled.
* @hide
*/
public static final int CALLBACK_TRAVERSAL = 3;
/**
* Callback type: Commit callback. Handles post-draw operations for the frame.
* Runs after traversal completes. The {@link #getFrameTime() frame time} reported
* during this callback may be updated to reflect delays that occurred while
* traversals were in progress in case heavy layout operations caused some frames
* to be skipped. The frame time reported during this callback provides a better
* estimate of the start time of the frame in which animations (and other updates
* to the view hierarchy state) actually took effect.
* @hide
*/
public static final int CALLBACK_COMMIT = 4;
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
上图可以看出有4种类型:输入、动画、插画动画(insets_animation这么翻译应该是对的吧?),提交(commit?应该是要在绘制结束后进行一些日志输出、告警什么的)。
回到最终boss--doFrame方法:
可以看到,这些不同类型的队列组成数组mCallbackQueues,最后执行也是按照一定顺序去执行的,input最先执行,动画其次,然后是traversal(就是我们scheduleTraversals方法入队时的类型),最后是commit。
doCallbacks的源码如下,其实就是把指定类型的队列从mCallbackQueues中弹出,然后按照一定条件依次执行这些Runnable:
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
// Update the frame time if necessary when committing the frame.
// We only update the frame time if we are more than 2 frames late reaching
// the commit phase. This ensures that the frame time which is observed by the
// callbacks will always increase from one frame to the next and never repeat.
// We never want the next frame's starting frame time to end up being less than
// or equal to the previous frame's commit frame time. Keep in mind that the
// next frame has most likely already been scheduled by now so we play it
// safe by ensuring the commit time is always at least one frame behind.
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
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));
}
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);
}
}
在最后,我们的收获,先从总体上概括:
- View的绘制流程由ViewRootImpl发起很多,ViewRootImpl持有了一个Choreographer,ViewRootImpl很多public方法都会触发performTraveral方法,performTraveral是绘制的入口方法。
- Choreographer英文是编排者的意思,这里负责收集ViewRootImpl传过来的行为,而具体的执行时间则由Choreographer来控制。
- Choreographer持有一个数组,数组的每个元素是一个行为单向链表队列。链表节点包含目标执行时间,Choreographer可以由此判断行为需要在哪一帧去执行并按链表顺序执行。
- Choreographer要处理垂直同步信号,也可以处理animation、inset-animation。