从问题出发🤔
我们知道UI绘制的主要入口是ViewRootImpl.scheduleTraversals
,在这个方法中会向Choreographer
注册一个回调,在回调方法中进行UI绘制操作,那么问题来了,在调用scheduleTraversals
之后如果有大量的消息进入消息队列中,是否会卡住UI绘制?它能否保证UI绘制优先?带着问题读一下源码(Android-31)
阅读源码🧐
首先看scheduleTraversals
方法
ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//往MessageQueue发送一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向Choreographer注册CALLBACK_TRAVERSAL类型的回调:mTraversalRunnable,在其run方法中会调用doTraversal进行UI绘制
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
.....
//UI绘制
performTraversals();
.....
}
}
在scheduleTraversals和doTraversal中出现了消息队列中同步屏障的概念,后面会讲到,这里先看Choreographer.postCallback
做了什么
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;
//根据callbackType的类型添加到不同的回调队列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//当delayMillis为0时(上文传递的为0)
if (dueTime <= now) {
scheduleFrameLocked(now);
}
//当delayMillis不为0时,实际上也是走到scheduleFrameLocked方法中去
else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
//为message设置异步标志
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
//判断当前线程是否为Choreographer的工作线程,实际上也就是判断是否为主线程,这里为true
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
}
//线程不对应,实际上也是走到scheduleVsyncLocked方法中去
else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
//为message设置异步标志
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);
//为message设置异步标志
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
private void scheduleVsyncLocked() {
......
mDisplayEventReceiver.scheduleVsync();
......
}
mDisplayEventReceiver
的类型是定义在Choreographer
中的内部类FrameDisplayEventReceiver
,继承自DisplayEventReceiver
,并且实现了Runnable接口,其scheduleVsync方法定义在父类DisplayEventReceiver中
DisplayEventReceiver.java
public void scheduleVsync() {
......
nativeScheduleVsync(mReceiverPtr);
}
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
}
nativeScheduleVsync
是个native方法,用于向SurfaceFlinger
注册下一次Vsync信号的监听,在信号到来的时候native会调用onVsync
方法,具体实现看FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, 0);
}
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
.......
long now = System.nanoTime();
//Vsync时间戳大与当前时间,即信号在未来的时间,打印日志
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;
}
//是否有正在处理的Vsync信号
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;
mLastVsyncEventData = vsyncEventData;
//创建消息,callback为this,也即回调run方法
Message msg = Message.obtain(mHandler, this);
//将消息标记为异步
msg.setAsynchronous(true);
//发送消息,指定时间为Vsync信号的时间
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
......
}
@Override
public void run() {
mHavePendingVsync = false;
//调用Choreographer的doFrame方法
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
在onVsync通过向mHandler发送消息确保run方法中的代码运行在mHandler绑定的Looper对应的线程,也即主线程中,接下来看Choreographer的doFrame方法
Choreographer.java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
try {
.......
synchronized (mLock) {
if (!mFrameScheduled) {
traceMessage("Frame not scheduled");
return; // no work to do
}
.......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
//判断(当前时间 - vsync信号时间)是否大于一帧间隔,也即是否丢帧了
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
//丢的帧数达到限制了就打印日志,老熟人了
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;
.......
//将frameTimeNanos修正到离当前时间最近一帧的时间戳
frameTimeNanos = startNanos - lastFrameOffset;
}
//当前帧时间戳比上一帧的时间戳小
//说明当前的Vsync信号比上一个记录的Vsync信号要旧,重新注册一个Vsync信号监听
if (frameTimeNanos < mLastFrameTimeNanos) {
......
scheduleVsyncLocked();
return;
}
//mFPSDivisor用来放大帧间隔时间,跳过部分帧
//假设frameIntervalNanos为1秒,则每分钟60帧,
//当mFPSDivisor设置为2之后,就成了2秒响应一帧,每分钟成了30帧
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);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
}
.......
}
//根据callbackType通知对应的监听者
void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
.......
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
}
}
至此流程就串联起来了
- ViewRootImpl#scheduleTraversals 向消息队列发送一个同步屏障,然后向Choreographer注册CALLBACK_TRAVERSAL类型的监听
- Choreographer向FrameDisplayEventReceiver注册下一次Vsync信号的监听
- FrameDisplayEventReceiver向SurfaceFlinger注册下一次Vsync信号的监听,在信号到来时native回调其onVsync方法
- FrameDisplayEventReceiver的onVsync中向Handler发送一个异步消息,在消息回调中将事件通知给Choreographer的各个类型的监听者
- ViewRootImpl的mTraversalRunnable的run方法被调用,方法内会调用doTraversal方法,移除消息队列中的同步屏障,触发布局绘制流程
从代码中可以看到,doTraversal并不是在scheduleTraversals之后立刻执行,属于异步回调,那么在回调之前往主线程的消息队列发送大量消息完全有卡住UI绘制的条件,事实是这样吗?
同步屏障和异步消息
ViewRootImpl.scheduleTraversals 向消息队列发送一个同步屏障,看看这个同步屏障有什么作用
MessageQueue.java
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//创建同步屏障并插入到队列中,消息的target为null表示这是一个消息屏障
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) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
Message next() {
.......
for (;;) {
.......
synchronized (this) {
.......
Message prevMsg = null;
Message msg = mMessages;
//如果队首是同步屏障,则只从队列中取标记为异步的消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
......
return msg;
}
......
}
}
当消息队列的队首是一个同步屏障时,只会从队列中取出异步消息。也就是说当ViewRootImpl.scheduleTraversals
向消息队列中插入同步屏障之后,插入消息队列的消息需要是异步的才有机会执行,直到ViewRootImpl.doTraversal
移除同步屏障才恢复正常。可以看到Choreographer中的消息都通过Message.setAsynchronous(true)标记为了异步消息,则这些与触发UI绘制息息相关的消息是会得到执行的,从而保证了UI绘制的优先
结论🏄♂️
可以得出结论,即使ViewRootImpl.scheduleTraversals执行之后我们往消息队列中插入大量消息也不会影响UI绘制,除非我们也把消息设置成异步
思考💭
我们在开发中可以利用同步屏障这个特性吗?
很遗憾MessageQueue.postSyncBarrier是个hide方法,不过如果把方法公开,这里来个屏障那里也来个屏障,不就全乱套了吗
那在开发过程中有什么方法可以提升消息的优先级吗?
Handler中有个sendMessageAtFrontOfQueue(message)方法可以将消息插入队列的头部优先执行,对应的api还有postAtFrontOfQueue(Runnable)
作者:今天想吃什么
链接:https://juejin.cn/post/7124690172217655304
网友评论