//ViewRootImpl @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread();//检查是否在主线程 mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。 scheduleTraversals(); } } void scheduleTraversals() { if (!mTraversalScheduled) {//同一帧内不会多次调用遍历 mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//拦截同步Message //探索的入口 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() { mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//移除同步消息屏障 performTraversals();//View的绘制流程正式开始。 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
针对以下 2 个要点分析
① postSyncBarrier
简单来说,同步屏障的作用是可以拦截 Looper 对同步消息的获取和分发,因为 Handler 消息机制中,Looper 会不断的从 MessageQueue 中取出 Message。加入同步屏障之后,Looper 只会获取和处理异步消息
这么做的原因是 View 的绘制和屏幕刷新优先级肯定是最高的(也就是 VIP ,防止卡顿), 除了对View绘制渲染的处理操作可以优先处理(设置为异步消息),其它的 Message 都可以放置一边。保障弱势群体的权益。① Choreographer
Choreographer
Choreographer 是 Jelly Bean(Android 4.1)中黄油计划(Project Butter)引入的产物,包括 :
- Choreographer : 负责统一动画、输入和绘制时机。
- VSYNC : 垂直同步信号。
- Triple Buffer : 第三块绘制Buffer,减少显示内容的延迟。
在ViewRootImpl
中,scheduleTraversals()
方法调用Choreographer 的 postCallback() 方法传入将要执行遍历绘制的 runnable。
也可以这么说 : ViewRootImpl 的遍历绘制doTraversal()
方法,由编舞者 Choreographer 主导,在时机成熟的时候
//Choreographer //Posts a callback to run on the next frame. 也就是绘制下一帧的内容 public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0);//延迟时间为0 } public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { `````` postCallbackDelayedInternal(callbackType, action, token, delayMillis); } private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; //根据时间将 action 添加到 mCallbackQueue 的队列中 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { //因为传入的延迟时间delayMillis 为 0 scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; //设置异步延迟消息 ,过dueTime后执行(无视同步屏障) msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
在postCallbackDelayedInternal()
方法中,我们注意到 :
mCallbackQueues
doTraversal()
方法的那个runnable)。在时机成熟的时候,mCallbackQueues 会回调这些 action 。setAsynchronous
紧接着上面调用的scheduleFrameLocked()
方法 :
//Choreographer private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) {//4.1及以上默认使用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();// UI 线程 } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); //将异步消息放置Handler队列的最前面,当前是最高优先级。 mHandler.sendMessageAtFrontOfQueue(msg); } } `````` } } private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } //只能在ui线程中使用 private final FrameDisplayEventReceiver mDisplayEventReceiver;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
//DisplayEventReceiver /** * Schedules a single vertical sync pulse to be delivered when the next * display frame begins. */ public void scheduleVsync() { //注册一个垂直同步脉冲VSYNC,当下一个脉冲到来时会回调dispatchVsync方法 nativeScheduleVsync(mReceiverPtr); } private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
从上面代码可以看出,Choreographer 即将要执行 垂直同步VSYNC信号了,如果当前在主线程,则立即调用scheduleVsyncLocked()
方法,不是主线程则通过Handler(mainLooper)切换到 UI 线程,并且该Message是异步消息且放置消息队列的第一个,是最高优先级要处理的事情。
FrameDisplayEventReceiver
FrameDisplayEventReceiver 继承 DisplayEventReceiver , 主要是用来接收同步脉冲信号 VSYNC。
scheduleVsync()
方法通过底层nativeScheduleVsync()
向SurfaceFlinger 服务注册,即在下一次脉冲接收后会调用 DisplayEventReceiver的dispatchVsync()
方法。
这里类似于订阅者模式,但是每次调用nativeScheduleVsync()
方法都有且只有一次dispatchVsync()
方法回调。
VSYNC
VSYNC
VSYNC 的全称是 Vertical Synchronization ,即垂直同步。
由于人眼与大脑之间的协作一般情况无法感知超过60FPS的画面更新。如果所看到画面的帧率高于12帧的时候,就会认为是连贯的,达到24帧便是流畅的体验,这也就是胶片电影的播放速度(24FPS)
对于屏幕显示,游戏体验来说,如果能整体平稳的达到60FPS,画面每秒更新60次,也就是16.67ms刷新一次,绝大部分人视觉体验都会觉得非常流畅如丝般顺滑。
Android 亦如此,正常情况下屏幕刷新频率也是60FPS
//获取手机屏幕刷新频率 Display display = getWindowManager().getDefaultDisplay(); float refreshRate = display.getRefreshRate();
- 1
- 2
- 3
- 4
//Display public float getRefreshRate() { return mRefreshRate; } //Display 内部类 public static final class Mode implements Parcelable { private final float mRefreshRate; public Mode(``````, float refreshRate) { mRefreshRate = refreshRate; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在VirtualDisplayDevice 类中,对Mode进行赋值,并且refreshRate
Ϊint
final
常量值,也就是60HZ
//VirtualDisplayDevice private static final float REFRESH_RATE = 60.0f;
- 1
- 2
- 3
每秒钟 60 帧的屏幕刷新频率,也就是 1000 / 60 ≈ 16.67ms 。
- 时间从0开始,进入第一个16ms:Display显示第0帧,CPU,GPU处理第一帧。
- 时间进入第二个16ms:因为早在上一个16ms时间内,第1帧已经由CPU,GPU处理完毕。Display可以正常显示第1帧。但在当前的 16ms期间,CPU和GPU却并未及时绘制第2帧数据(注意前面的空白区),而是在本周期快结束时,CPU/GPU才去处理第2帧数据。
- 时间进入第三个16ms,此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次(对应时间段上标注了一个Jank)。
所以关键点在于 CPU 在每个 VSYNC 信号到来时,必须开始着手处理下一帧,然后交由 GPU 处理,最后再由屏幕(display)显示出来,整个流水线作业的步调一致是保证显示系统流畅的基础。并且程序的大多数 UI 操作都要在 16.67ms内执行完成。

所以在主线程中的耗时操作会影响 ui 的流畅度。
dispatchVsync()
方法,也就是上文所说的时机成熟的时候。
//DisplayEventReceiver /** * Schedules a single vertical sync pulse to be delivered when the next * display frame begins. */ public void scheduleVsync() { //注册一个垂直同步脉冲VSYNC,当下一个脉冲到来时会回调dispatchVsync方法 nativeScheduleVsync(mReceiverPtr); } private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
//Choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { `````` mTimestampNanos = timestampNanos;//信号到来的时间参数 mFrame = frame; Message msg = Message.obtain(mHandler, this);//this 就是当前的 run 方法 msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);//切换到主线程,即执行下面的 run 方法 } @Override public void run() { doFrame(mTimestampNanos, mFrame); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
onVsync()
run()
run()
//Choreographer void doFrame(long frameTimeNanos, int frame) { try { mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);//scheduleTraversals 方法postCallback的标识 doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } } void doCallbacks(int callbackType, long frameTimeNanos) { `````` try { for (CallbackRecord c = callbacks; c != null; c = c.next) { c.run(frameTimeNanos); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
ViewRootImpl
//ViewRootImpl void scheduleTraversals() { if (!mTraversalScheduled) {//在下一帧中,只会执行一次 doTraversal 遍历操作 ! mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, //doCallbacks中回调CALLBACK_TRAVERSAL标识 mTraversalRunnable, null); `````` } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal();//遍历视图 measure,layout,draw } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
兜了一圈回到了ViewRootImpl
,上面大致说明了我们当前的遍历操作,对下一帧的准备工作是,当我们ViewRootImpl
如果迟迟交不出View的绘制结果,那么屏幕将会一直显示当前画面。
Triple Buffer
Android 4.1 以前一直沿用double-buffer 双缓冲技术,也就是两块显示 Buffer,back buffer用于CPU/GPU下一帧的绘制准备,另一块 frame buffer 则用于屏幕显示,当back buffer准备就绪后,它们才进行交换。
主线程耗时阻塞,xml 布局文件层次过多冗余臃肿,绘制操作不当(onDraw中频繁创建对象)
- 当CPU / GPC 准备B Buffer 内容时间过长,导致第一个VSYNC信号到来时不能交付 back Buffer ,那么屏幕上显示的还是之前的那块 PRE Buffer , 并且 B Buffer 内容准备完成后,还需要等待下一个 VSYNC 信号才能交付。
- 因为在第二个 VSYNC 信号到来时,两块 Buffer 都已经被占用(一块用来显示 ,一块被 B Buffer 准备工作持有),所以当下一次绘制内容也存在延迟的情况也会造成连锁卡顿。(同一帧画面显示 2 次及以上)
解决上面问题的办法就是引入第三块 Buffer , 在渲染 B 超时而且 Buffer A 又用于屏幕显示时,可以用第三块 Buffer 来进行C 的准备工作,这样便减少了后面的一次 Jank 发生。
系统大部分情况下都会使用两个Buffer 来完成显示,只有在某一帧的处理时间超过 2 次 VSYNC 信号周期才会使用第三块 Buffer。
最后补上一张本文整个流程的大致时序图,下篇文章将会继续探索CPU / GPU 渲染相关知识。