一. 渲染基本概念

对于渲染来说在开始前我们先了解几个概念:

CPU主要负责包括 MeasureLayoutRecordExecute 的计算操作。

GPU主要负责 Rasterization(栅格化)操作。栅格化是指将向量图形格式表示的图像转换成位图(像素)以用于显示设备输出的过程,简单来说就是将我们要显示的视图,转换成用像素来表示的格式。

帧率代表了GPU在一秒内绘制操作的帧数。

刷新率代表了屏幕在一秒内刷新屏幕的次数,Android手机一般为60HZ。

二. Android黄油计划

涉及到滑动流畅,Android在谷歌4.1版本引入了黄油计划。其中有三个重要的核心元素:VSYNC、缓存区和Choreographer:

2.1 VSYNC信号

在Android4.0的时候,CPU可能会因为在忙其他的事情,导致没来得及处理UI绘制。为了解决这个问题,设计成系统在收到VSYN信号后,才会开始下一帧的渲染。也就是收到VSYN通知,CPU和GPU才开始计算然后把数据写入buffer中。

VSYN信号是由屏幕产生的,并且以60fps的固定频率发送给Android系统,在Android系统中的SurfaceFlinger接收发送的Vsync信号。当屏幕从缓存区扫描完一帧到屏幕上之后,开始扫描下一帧之前,发出的一个同步信号,该信号用来切换前缓冲区和后缓冲区。

在引入了Vsyn信号之后,绘制就变成了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DcWnksFU-1664701709409)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d7f6c7ca88547f6b424d120635da7a0~tplv-k3u1fbpfcp-watermark.image?)]

可以看到渲染的时候从第0帧开始,CPU开始准备第一帧的图形处理,好了才交给GPU进行处理,再上一帧到来之后,CPU就会开始第二帧的处理,基本上跟Vsync的信号保持同步。

有了Vsync机制,可以让CPU/GPU有完整的16ms时间来处理数据,减少了jank。

2.2 三重缓存

在采用双缓冲机制的时候,也意味着有两个缓存区,分别是让绘制和显示器拥有各自的buffer,GPU使用Back Buffer进行一帧图像数据写入,显示器则是用Frame Buffer,一般来说CPU和GPU处理数据的速度视乎都能在16ms内完成,而且还有时间空余。但是一旦界面比较复杂的情况,CPU/GPU的处理时间超过了16ms,双缓冲开始失效了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmBD8MFY-1664701709412)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b3a8ab39c0c4d11a56cac997047e507~tplv-k3u1fbpfcp-watermark.image?)]

在第二个时间段内,因为GPU还是处理B帧,数据没有及时交换,导致继续系那是之前A缓存区中的内容。

在B帧完成之后,又因为缺少了Vusnc信号,只能等待一段时间。

直到下一个Vsync信号出现的时候,CPU/GPU才开始马上执行,由于执行时间仍然超过了16ms,导致下一次应该执行的缓存区交换又被推迟了,反复这种情形,就会出越来越多的jank。

为了解决这个问题,Android 4.1才引入了三缓冲机制:在双缓冲机制的基础上增加了一个Graohic Buffer缓冲区,这样就可以最大限度的利用空闲的时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MHvDEdXr-1664701709413)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1a1f6fe8d05c4fa5ba8272232562491f~tplv-k3u1fbpfcp-watermark.image?)]

可以看到在第二个时间段里有了区别,在第一次Vsync发生之后,CPU不用再等待了,它会使用第三个bufferC来进行下一帧的准备工作。整个过程就开始的时候卡顿了一下,后面还是很流畅的。但是GPU需要跨越两个Vsync信号才能显示,这样就还是会有一个延迟的现象。

总的来说三缓冲有效利用了等待vysnc的时间,减少了jank,但是带来了lag。

2.3 Choreographer

在了解了Vsync机制后,上层又是如何接受这个Vsync信号的?

Google为上层设计了一个Choreographer类,翻译成中文是“编舞者”,是希望通过它来控制上层的绘制(舞蹈)节奏。

可以直接从其构造函数开始看起:

private Choreographer(Looper looper, int vsyncSource) {//创建Looper对象mLooper = looper;//接受处理消息mHandler = new FrameHandler(looper);//用来接受垂直同步脉冲,也就是Vsync信号mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;mLastFrameTimeNanos = Long.MIN_VALUE;
//计算下一帧的时间,Androoid手机屏幕是60Hz的刷新频率mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//初始化CallbackQueue,将在下一帧开始渲染时回调mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];for (int i = 0; i <= CALLBACK_LAST; i++) {mCallbackQueues[i] = new CallbackQueue();}// b/68769804: For low FPS experiments.setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));}

主要来看下FrameHandlerFrameDisplayEventReceiver的数据结构:

private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {//开始渲染下一帧的操作case MSG_DO_FRAME:doFrame(System.nanoTime(), 0);break;//请求Vsync信号case MSG_DO_SCHEDULE_VSYNC:doScheduleVsync();break;//请求执行Callbackcase MSG_DO_SCHEDULE_CALLBACK:doScheduleCallback(msg.arg1);break;}}}

FrameHandler可以看到对三种消息进行了处理,对其具体实现一会分析。

private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);}@Overridepublic void onVsync(long timestampNanos, long physicalDisplayId, int frame) {......mTimestampNanos = timestampNanos;mFrame = frame;//将本身作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}

可以看出来这个类主要是用来接收底层的VSync信号开始处理UI过程。而Vsync信号是由SurfaceFlinger实现并定时发送,接收到之后就会调用onVsync方法,在里面进行处理消息发送到主线程处理,另外在run()方法里面执行了doFrame(),这也是接下来要关注的重点方法。

2.3.1 Choreographer执行过程

ViewRootImpl 中调用 Choreographer 的 postCallback 方法请求 Vsync 并传递一个任务(事件类型是 Choreographer.CALLBACK_TRAVERSAL)

最开始执行的是postCallBack发起回调,这个FrameCallback将会在下一帧渲染时执行。而其内部又调用了postCallbackDelayed方法,在其中又调用了postCallbackDelayedInternal方法:

private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long 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);}}}

在这里执行了时间的计算,如果立即就会调用scheduleFrameLocked方法,不然就会延迟发送一个MSG_DO_SCHEDULE_CALLBACK消息,并且在这里使用msg.setAsynchronous(true)讲消息设置成异步。、

而所对应的mHandle也就是之前的FrameHandler,根据消息类型MSG_DO_SCHEDULE_CALLBACK,最终会调用到doScheduleCallback方法:

void doScheduleCallback(int callbackType) {synchronized (mLock) {if (!mFrameScheduled) {final long now = SystemClock.uptimeMillis();if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {scheduleFrameLocked(now);}}}}

到了这一步看到还是会调用到scheduleFrameLocked方法。

private void scheduleFrameLocked(long now) {if (!mFrameScheduled) {mFrameScheduled = true;if (USE_VSYNC) {//开启了Vsyncif (DEBUG_FRAMES) {Log.d(TAG, "Scheduling next frame on vsync.");}if (isRunningOnLooperThreadLocked()) {//申请Vsync信号scheduleVsyncLocked();} else {//最终还是会调用到scheduleVsyncLocked方法Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);msg.setAsynchronous(true);mHandler.sendMessageAtFrontOfQueue(msg);}} else {//如果没有直接使用Vsync的话,则直接通过该消息执行doFramefinal 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);}}}

在这里对是否使用Vsync信号进行处理,如果没有使用则直接通过消息执行doFrame。如果使用的就会先判断是否在当前Looper线程中运行,如果在的话就会请求Vsync信号,否则发送消息到 FrameHandler。直接来看下scheduleVsyncLocked方法:

 private void scheduleVsyncLocked() {mDisplayEventReceiver.scheduleVsync();}

可以看到调用了FrameDisplayEventReceiverscheduleVsync方法,通过查找在其父类DisplayEventReceiver中找到了scheduleVsync方法:

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 {//申请VSYNC信号,会回调onVsunc方法nativeScheduleVsync(mReceiverPtr);}}

scheduleVsync()就是使用native方法nativeScheduleVsync()去申请VSYNC信号。等下一次信号接收后会调用dispatchVsync 方法:

private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {onVsync(timestampNanos, physicalDisplayId, frame);}

这个onVsync方法最终实现也就是在FrameDisplayEventReceiver里。可以知道最终还是走到了doFrame方法里。

void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {if (!mFrameScheduled) {return; // no work to do}......//设置当前frame的Vsync信号到来时间     long intendedFrameTimeNanos = frameTimeNanos;startNanos = System.nanoTime();final long jitterNanos = startNanos - frameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {//时间差大于一个时钟周期,认为跳frame    final long skippedFrames = jitterNanos / mFrameIntervalNanos;//跳frame数大于默认值,打印警告信息,默认值为30   if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {Log.i(TAG, "Skipped " + skippedFrames + " frames!  "+ "The application may be doing too much work on its main thread.");}//计算实际开始当前frame与时钟信号的偏差值  final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;if (DEBUG_JANK) {Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "+ "which is more than the frame interval of "+ (mFrameIntervalNanos * 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.");}//请求下一次时钟信号  scheduleVsyncLocked();return;}......//记录当前frame信息    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);mFrameScheduled = false;//记录上一次frame开始时间,修正后的  mLastFrameTimeNanos = frameTimeNanos;}try {//执行相关callBack   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);}}

doFrame方法对当前帧的运行时间进行了一系列判断和修正,最终顺序执行了五种事件回调。

  1. CALLBACK_INPUT:输入
  2. CALLBACK_ANIMATION:动画
  3. CALLBACK_INSETS_ANIMATION:插入更新的动画
  4. CALLBACK_TRAVERSAL:遍历,执行measure、layout、draw
  5. CALLBACK_COMMIT:遍历完成的提交操作,用来修正动画启动时间

接着就会执行doCallbacks方法:

void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;......try {Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);//迭代执行所有队列任务for (CallbackRecord c = callbacks; c != null; c = c.next) {.....//调用CallbackRecord内的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);}
}

主要是去遍历CallbackRecrd,执行所有任务:

private static final class CallbackRecord {public CallbackRecord next;public long dueTime;public Object action; // Runnable or FrameCallbackpublic Object token;@UnsupportedAppUsagepublic void run(long frameTimeNanos) {if (token == FRAME_CALLBACK_TOKEN) {((FrameCallback)action).doFrame(frameTimeNanos);} else {((Runnable)action).run();}}}

最终actionrun方法会被执行,这里的action也就是我们在前面调用psetCallback传进来的,也就是 ViewRootImpl 发起的绘制任务mTraversalRunnable了。

然后这里又一次调用了doFrame方法,在啥时候token会是FRAME_CALLBACK_TOKEN呢? 可以发现在我们调用postFrameCallback内部会调用postCallbackDelayedInternal进行赋值:

 public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {if (callback == null) {throw new IllegalArgumentException("callback must not be null");}postCallbackDelayedInternal(CALLBACK_ANIMATION,callback, FRAME_CALLBACK_TOKEN, delayMillis);}

ChoreographerpostFrameCallback()通常用来计算丢帧情况。

知道了Choreographer是上层用来接收VSync的角色之后,我们需要进一步了解VSync信号是如何控制上层的绘制的。而绘制UI的起点是View的requestLayout或者是invalidate方法被调用触发,好了时间不早了,这些就放在下一篇Android的屏幕刷新机制里解释吧。(刷新流程和同步屏障)

三. 小结

Android在黄油计划中引入了三个核心元素:VSYNCTriple BufferChoreographer

VSYNC 信号是由屏幕(显示设备)产生的,并且以 60fps 的固定频率发送给 Android 系统,Android 系统中的 SurfaceFlinger 接收发送的 VSYNC 信号。VSYNC 信号表明可对屏幕进行刷新而不会产生撕裂。

三重缓存机制(Triple Buffer) 利用 CPU/GPU 的空闲等待时间提前准备好数据,有效的提升了渲染性能。

又介绍了 Choreographer ,它实现了协调动画(animations)、输入(input)、绘制(drawing)三个UI相关的操作。

参考

Android 显示刷新机制、VSYNC和三重缓存机制

Android图形显示系统(一)

Android屏幕刷新机制

Android Choreographer 源码分析

“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解!

用力一瞥Android渲染机制-黄油计划相关推荐

  1. Android渲染机制和丢帧分析

    http://blog.csdn.net/bd_zengxinxin/article/details/52525781 自己编写App的时候,有时会感觉界面卡顿,尤其是自定义View的时候,大多数是因 ...

  2. Android Project Butter 黄油计划

    深入剖析android新特性 笔记 9.3 Project Butter 黄油计划 Android4.1 Jelly Bean引入了ProjectButter 先说背景,再讲解为什么ProjectBu ...

  3. 深入Android渲染机制

    1.知识储备 CPU: 中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU将对象处理为多维图形,纹理(Bitmaps.Drawables等都是一起打包到统一的纹理). GPU:一个类似于 ...

  4. Android 渲染机制——SurfaceFlinger

    SurfaceFlinger Android 图形架构使用了生产者--消费者模型.Surface 表示缓冲队列中的生产方,图像流最常见的消耗方是 SurfaceFlinger,该系统服务接收来自于多个 ...

  5. Android UI 渲染机制的演进,你需要了解什么?

    前言 如今UI 渲染可能是诸多性能问题中最容易被察觉到的,Android 开发既要面对各式各样的手机屏幕尺寸和分辨率,还要与"凶残"的产品和 UI 设计师过招. 在正确实现复杂.炫 ...

  6. Android UI优化—从Android渲染原理理解UI卡顿

    Android渲染机制 1.Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染 2.渲染的过程是由CPU与GPU协作完成 如下图: 为什么是16ms? 1.人眼与大脑之间的协作无法感 ...

  7. 关于Android Framework渲染机制,你需要学习哪些?

    聊到Android的渲染流程部分,部分Android开发脑海中估计又会想起面试官在面试时提出的那些死亡面试题: Android渲染的整体架构是怎样的? Android渲染的生产者包括哪些?Skia与O ...

  8. Android性能优化(4):UI渲染机制以及优化

    文章目录 1. 渲染机制分析 1.1 渲染机制 1.2 卡顿现象 1.3 内存抖动 2. 渲染优化方式 2.1 过度绘制优化 2.1.1 Show GPU overdraw 2.1.2 Profile ...

  9. android屏幕渲染机制

    优化性能一般从渲染,运算与内存,电量三个方面进行,今天开始说聊一聊Android的渲染机制,我们要知道Android系统每隔16ms就重新绘制一次Activity,也就是说,我们的应用必须在16ms内 ...

最新文章

  1. 假如AI也会diss人类,他们会这样.....
  2. Retrofit全攻略——进阶篇
  3. U盘制做DOS启动盘
  4. Apex 的异常处理
  5. java pdf文件压缩_PDF文件压缩转换教程
  6. vue-day03-vue组件化开发
  7. puppet运行慢的一个小例子
  8. type或者xtype总结
  9. c语言中L''作用,L/C问题: 请问L/C上的49:Confirmation Instruction 有什么作用啊[1]
  10. 【优化电价】基于matlab遗传算法求解共享汽车电价优化问题【含Matlab源码 1162期】
  11. 稳定婚姻问题和Gale-Shapley算法(转)
  12. 通过PPC来播放PC声音
  13. 电脑怎么设置固定静态ip地址
  14. 在阿里云轻量应用服务器上安装爱快软路由
  15. 论文详解-MolGPT: Molecular Generation Using a Transformer-Decoder Model
  16. 30个专业配色网站, 让你配色从此更专业
  17. MATLAB 使用GUI设计简单的计算器
  18. cocoscreator中tween详细用法
  19. 广西首届网络安全选拔赛 MISC Wirteup
  20. 中台为什么做不好?拆系统“烟囱”容易,拆思维“烟囱”难!

热门文章

  1. 计算机屏幕闪烁黑屏,台式机电脑。显示屏指示灯一直闪烁,屏幕黑屏。。...-显示器电源灯闪黑屏...
  2. java获取一天的开始时间和结束时间
  3. Jenkins搭建.NET自动编译测试并实现半增量部署
  4. 网卡和网卡的驱动程序
  5. SAS学习——系统选项
  6. 高等数学拾遗 矢量分析
  7. 达人评测 i7 12700H和R7 6800H选哪个好
  8. ARM64(M1版)Mac运行MAA以及AzurLaneAutoScript自动化打明日方舟和碧蓝航线
  9. Android进阶宝典—App响应时间优化
  10. 蚂蚱蚂蚱,我的骄傲放纵。