View的绘制过程主要都包含在ViewRootImpl#performTraversal方法内,这个方法内主要包括了measure、layout、draw这三个步骤,具体就不放在这里讲述了。我们主要关注下,每次绘制时Framework内是怎么发起到调用的。
1. requestLayout请求绘制
app侧不论是调用requestLayout或是调用invalidate方法来触发重绘,最终都会调用schedueTraversal
方法,开始绘制流程,只不过是在该流程中决定是否执行measure、layout或者draw。
ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
我们重点看下scheduleTraversals方法做了哪些工作
void scheduleTraversals() {
//mTraversalScheduled字段保证同时间内的多次修改只会执行一次渲染过程(如更新text时)
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 代码1.1
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 代码1.2
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//执行绘制过程 包括:measure、layout、draw过程
performTraversals();
}
}
这里的mTraversalRunnable就一个包含调用doTraversal方法的线程,而doTraversal方法主要就是调用performTraversals来进行view的绘制过程
2.通过Handler放置同步屏障
首先这里会通过handler获取当前的messageQueue,并调用了postSyncBarrier方法
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
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;
}
}
2.1 什么是同步屏障
这里的syncBarrier就是同步屏障,看着名字很唬人,其实他就是一个target为null的message 普通消息有target是因为它需要将消息分发给对应的target,而同步屏障不需要被分发。
MessageQueue内的,我们之前在学习Handler的时候,就应该记得通过handler发送message的时候会对target进行检测,如果发现当前msg的target为null,会抛异常
正常通过sendMessage的时候调用流程如下:
sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage
//将当前msg入队 会检测msg是否满足条件
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
...
}
正常通过handler发送流程如上,而放置同步屏障是直接在postSyncBarrier方法内将当前target为null的msg放置在队列头部。表明当前系统需要开始进行绘制流程。
2.2 同步屏障作用
同步屏障的作用是保证当Vsync信号到来的时候,当前进程能够第一时间执行渲染流程, 而不是再去执行别的线程抛给handler的同步任务。在MessageQueue的next方法中可以体现
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的target为null,那么就说明该msg就是放置的一个同步屏障,后面的do-while循环就会找到第一个异步的msg,也就是包含绘制信息的msg。
2.3 向编舞者抛一个用于绘制的callback
在上述代码1.2的地方又向编舞者mChoreographer发送了一个callback,传递的是当前用来绘制的线程mTraversalRunnable,他会在下一帧到来时候进行回调, 即在下一个 VSync 信号到来时会执行TraversalRunnable-->doTraversal()-->performTraversals()-->绘制流程
Choreographer,即编舞者,控制整个系统内的渲染绘制的操作,协调动画、输入以及绘制的时机,每个主线程(Looper)都会有自己的编舞者,他是线程单例的(基于ThreadLocal实现)...
Choreographer.java
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
...
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//根据回调方法的类型,将callback放置在不同的队列内
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now); //正常绘制是进入这里,开始请求下一次Vsync信号
} else {
//如果是想在后面某个时间绘制,会进入这里
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
//scheduleFrameLocked 会继续调用scheduleVsyncLocked();方法
//mDisplayEventReceiver是FrameDisplayEventReceiver类型
//它继承了FrameDisplayEventReceiver
private void scheduleVsyncLocked() {
mIsVsyncScheduled = true;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
mDisplayEventReceiver.scheduleVsync();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
//DisplayEventReceiver.java
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调用到native层,这里具体后续再写一篇文章总结下
nativeScheduleVsync(mReceiverPtr);
}
}
请求Vsync信号的过程这里不再阐述了,反正就是会在native层请求Vsync信号(requestNextVsync)
2.4 收到Vsync信号后的调用
当底层进行分发信号的时候,native层会去调用到DisplayEventReceiver的dispatchVsync
方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
onVsync(timestampNanos, physicalDisplayId, frame,
new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
}
//然后进入FrameDisplayEventReceiver重写的onVsync方法
//FrameDisplayEventReceiver.java 不仅继承了DisplayEventReceiver,
//同时它本身还是个runnable
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
....
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
...
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;
ScrollOptimizer.setVsyncTime(mTimestampNanos);
mLastVsyncEventData = vsyncEventData;
//将自身封装成msg
Message msg = Message.obtain(mHandler, this);
//将消息设置为异步消息
msg.setAsynchronous(true);
//通过handler发送 代码2.1
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
可以看到代码2.1处FrameDisplayEventReceiver向handler发送了一个异步消息,由于之前设置了同步屏障,在取消息的时候会默认找到第一个异步消息也就是当前msg,执行其run方法
我们来看下doFrame方法
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame " + vsyncEventData.id);
}
synchronized (mLock) {
mIsVsyncScheduled = false;
if (!mFrameScheduled) {
traceMessage("Frame not scheduled");
return; // no work to do
}
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
//是否当前执行的时候已经超过一帧的时间, 即使存在同步屏障,如果当前有正在执行的任务
//就有可能导致doFrame被延迟执行
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
//掉帧数如果超过30帧就打印log
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 % frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked(); //当前掉帧了,继续请求vsync信号
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastFrameIntervalNanos = frameIntervalNanos;
mLastVsyncEventData = vsyncEventData;
}
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
ScrollOptimizer.setUITaskStatus(true);
mFrameInfo.markInputHandlingStart();
//开始处理不同的callback方法
//输入事件的回调(最先执行)
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
//普通动画的回调
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
//Insets动画的回调
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
//这个是用来绘制的callback也就是ViewRootImpl刚才post的callback
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
//用来处理当前帧绘制完后的一些操作 运行在traversal之后
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
ScrollOptimizer.setUITaskStatus(false);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
if (getMainThreadInstance() == Choreographer.this) {
TurboSchedMonitor.getInstance().releaseAppToken();
}
}
...
mIsDoFrameProcessing = false;
}
继续看doCallback方法
void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
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();
//从对应callback队列内取出到达执行时间的callback ,封装成CallbackRecord
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]);
//遍历所有的callback
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));
}
//执行callback的run方法,也就是可以执行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);
}
}
到这里,一个完整的绘制线程的调用就结束了,然后就开始可以执行真正的绘制操作了。
这里就开始执行ViewRootImpl内的mTraversalRunnable线程,在该线程内的doTraversal方法内,会首先移除掉先前放置的同步屏障,保证后续handler的同步任务能够正常执行。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//真正绘制流程
performTraversals();
...
}
}
3 同步屏障可能导致的问题
同步屏障没用好会导致很严重的问题,如果某个子线程主动通过Choreographer来进行更新UI,但是由于某种原因导致放置的同步屏障未能remove掉,就会导致后续的所有同步任务都无法执行。
之前就接触过一例问题,就是由于系统主线程的同步屏障未能正常被remove,从而导致了所有的系统功能(依赖于handler的同步任务)都无法正常调用,排查了好久才定位到是由于同步屏障的原因。
因此,还是需要加深对同步屏障的理解。
下面用一张图来总结一下 Android绘制流程是如何借助同步屏障以及Choreographer 来完成绘制流程:
总结
- 同步屏障SyncBarrier的作用是保证当Vsync信号到来时,能够第一时间执行Choreographer向handler发送的异步消息,但是这不能保证当前的绘制任务一定能够在一帧内结束(可能是前一个同步任务事件较长或者当前的绘制任务耗时较长...)
- 当View请求刷新页面时,不会马上开始,他
需要先请求Vsync信号,等待Vsync信号到来时
才会开始进行计算屏幕数据、layout、measure以及draw等流程;这些流程结束后,新页面也不会立马显示到屏幕上
。需要等下一个Vsync信号到来时通过buffer缓存交换才能显示到屏幕上
- 如果当前屏幕没有任何改变的话,底层
也会进行每16.6ms进行一次页面的刷新过程
(通过Vsync信号来实现,但是app不会接收Vsync事件);也就是说,界面不变时屏幕也会保持每16.6ms的刷新频率,但是CPU/GPU不会执行绘制流程
- App侧可以通过postFrameCallback方法来监听界面的帧率,判断当前的丢帧情况
Ps: 本文参考了:juejin.cn/post/689420…