本文主要介绍Android图形的基础能力Vsync信号
- Android渲染系列(1)之原理概述篇
- Android渲染系列(2)之如何渲染UI
- Android渲染系列(3)之Choreographer
- Android渲染系列(4)之Surface与SurfaceFlinger
- Android渲染系列(5)之Window、Activity、View之间关系
- Android渲染系列(6)之Vsync
🔥面试官的小抄 面试进阶一网打尽,可能是东半球最好的面试资料
了解更多 加uestc_xsf(备注掘金进群) 技术交流、获取学习资料
Overview
早年的Android系统UI流畅性差的问题一直饱受诟病,Google为了解决这个问题在Android4.1 开发了Proiect Butter项目,也就是黄油计划,期望彻底改善Android系统的流畅性。这是Android UI系统的一次非常大的改进,了解改进的内容,是我们掌握Android渲染机制的关键。 概括来说在这次改进中,Google打出了一套VSync+Choreographer+TripleBuffer的组合拳。具体说来
- VSync:
它是黄油计划的核心,VSync (Vertical Synchronization 垂直同步) 是一种在PC时代就广泛使用的技术,简单说来它是利用屏幕刷新的间隙来进行帧缓冲区交换的技术。
- Choreographer:
Choreographer的引入是为了配合VSync,给App端一个稳定的染处理时机。
VSync的由来
撕裂感
我们知道屏幕上的画面每一帧都是静态的,不同的帧不断的进行刷新我们才能看的动态的画面, 我们来看两个概念:
- 屏幕刷新率(Hz): 屏幕在一秒内刷新的次数,Android手机一般都是60Hz,也就是一秒刷新60次,当然也有高刷的,但是60Hz足矣。
- 帧速率(FPS): cpu在一秒内合成的帧数,比如60FPS,就是60 frame per sconds,意思就是一秒合成60帧。
如上所述,当屏幕刷新率大于帧速率的时候,会发生卡顿;屏幕刷新率小于帧速率的时候,会发生撕裂。
无VSvnc机制时
这个图中有三个元素,Display 是显示屏幕,GPU 和 CPU 负责渲染帧数据,每个帧以方框表示,并以数字进行编号,如0、1、2。
当不使用VSvnc机制时,没有vsync的情况下,cpu在任意地方开始,随心所欲!
- 第一帧: CPU 正常执行帧1,GPU 正常渲染帧1,所以帧1正常显示。
- 第二帧:CPU 由于被占用等原因,等到即将显示帧2时,它才开始处理第二帧的内容,这显然完不成了,所以等到第二帧显示的时候,只能使用上一帧的内容显示了,也即是丢帧了。
为什么CPU没有及时处理呢,可能是CPU在忙于处理其他事情,忘记了处理UI绘制。当CPU想起来要去处理UI绘制时,又过了最佳时间段.
那么如何让 CPU 在新的一帧开始的时候立即处理显示内容呢?这时就需要 VSYNC 出手了
引入Vsync
VSYNC(vertical sync): 也就是垂直同步,当屏幕渲染完一帧数据后,即将开始渲染下一帧之前,发出的一个同步信号。
为此就引入了Vsync,VSvnc机制就是把UI体系的FPS和显示器的刷新频率同步起来,避免屏幕界面出现“断裂”的现象。 它类似于一种中断机制,通知CPU去处理UI绘制。cpu只要监听VSYNC信号,接收到信号后再开始交换后缓冲和前缓冲的数据,就等价于屏幕控制了数据交换,也就解决了撕裂问题,这很明显是设计模式中的监听器模式如下所示:
由于使用了VSync机制,
- 第0帧显示时候 CPU和GPU准备好了第一帧内容
- vsync信号来了之后,CPU立即处理第二帧显示任务
这里使用到了消息屏障机制,后面再另开文章介绍
这样第二帧显示之前 cpu和gpu也提前完成了显示任务的处理,让第二帧得以正常显示
可以看到,使用 VSYNC 信号机制,提升了渲染任务的优先级,优化了渲染性能,可有效的减少了丢帧、卡顿等问题。
但是上图中仍然存在一个问题:CPU 和 GPU 处理数据的速度似乎都能在 16ms 内完成,而且还有时间空余,也就是说,CPU 和 GPU 的帧率要高于 Display 的帧率。由于 CPU/GPU 只在收到 VSYNC 时才开始数据处理,故它们的帧率被拉低到与 Display 相同。但这种处理并没有什么问题,因为 Android 设备的 Display FPS 一般是 60,其对应的显示效果非常平滑。
但如果 CPU/GPU 的帧率小于 Display 的帧率,情况又不同了。
三重缓存机制
先来了解下双重缓存机制
双重缓存机制的不足
般我们在绘制 UI 的时候,都会采用一种称为“双缓存”的技术。 双缓存意味着要使用两个缓存区,其中一个称为 Front Buffer,另外一个称为 Back Buffer。 UI 总是先在 Back Buffer 中绘制,然后再和 Front Buffer 交换,渲染到显示设备中。
理想情况下,这样一个刷新会在 16ms 内完成,下图就是描述的这样一个刷新过程:Display 处理前 Front Buffer,CPU、GPU 处理 Back Buffer。
上述情况不卡顿都是建立在CPU和GPU都能在一个周期内 (16.67ms) 完成自己的工作, 双缓冲只是优化了卡顿问题,并没有根本解决卡顿问题,为何呢?我们先来大致说明一下Android的屏幕绘制流程(硬件加速情况下)
- 1 . 任何一个View都是依附于window的, 一个window对应一个surface
-
- view的measure、layout、draw等均是计算数据,这些是cpu干的事
-
- cpu把这些事干好后,在经过一系列计算将数据转交给gpu
-
- gpu将数据栅格化后,就交给SurfeceFlinger(以下简称SF)
- 5 SF将多个surfece数据合并处理后,就放入后缓冲区
- 6 屏幕以固定频率从前缓冲区拿出数据渲染,渲染完毕后发送VSYNC,此时前后缓冲区数据交换,屏幕绘制下一帧
上述是建立在开启硬件加速的情况下的,如果没有硬件加速,就去掉gpu部分,就可以简单理解为cpu直接将数据转交给sf,
这里软硬件加速暂时不展开了。
更多精彩内容关注公众号 Android茶话会
OK,我们简单整理一下数据的传递流程: CPU -> GPU -> Display,而且我们看到,cpu和gpu是排队工作的,它俩和屏幕是并行工作的。好,我们来看发生卡顿(jank)的场景:** CPU和GPU不能在一个周期内 (16.67ms) 完成自己的工作。**
如下所示: GPU没有在一个周期内处理完B,导致Display只能继续显示A, 这样AB两个Buffer (Android早期是Double Buffel机制,一个Back Buffer供GPU和CPU使用,一个Front Buffer供Display使用)都被占用了。 这样就导致CPU在第二个周期内无所事事。不仅如此,由于CPU在等待A释放,导致CPU绘制被延期,又导致了下一个Jank。
以上是使用双重缓存机制时产生的问题,那么又如何来解决呢? 为了解决这个问题,Android 引入了 Triple Buffer 机制。
三重缓冲机制(TripleBuffer)
这个时候如果有第三个Buffer,CPU就能去干活了。这也就是Tripple Buffer机制。 所以,Google 在 **Android4.1 **以后,引入了三重缓存机制:Tripple Buffer。Tripple Buffer 利用 CPU/GPU 的空闲等待时间提前准备好数据,但并不一定会使用。 如下所示:
我们看到,在第一次jank内,cpu使用了第三块缓冲区,自己计算了C帧的数据,
假如此时没有三缓冲,那么cpu就只能再继续等下一个vsync信号,也就是在图中蓝色A块的地方,才能开始计算C帧数据,就又引发下一次卡顿。
我们看到,通过引入三缓冲,虽然不能避免卡顿问题,但是却可以大幅优化卡顿问题,尤其是避免连续卡顿,但是,三缓冲也有缺点,就是内存消耗多,所以系统并非一直开启三缓冲,要想真正解决问题,还需要在cpu层对数据尽量优化,从而减小cpu和gpu的计算量,
比如:View尽量扁平化,少嵌套,少在UI线程做耗时操作等
Tips:
- Android 3.0引入了硬件加速(GPU)。
- Android 4.0默认开启了硬件加速。
- Android 4.1引入了黄油计划(VSYNC),上层开始接收VSYNC(Choreographer),并且加入了三缓冲.
- VSYNC不仅控制了后缓冲和前缓冲的数据交换,还控制了cpu何时开始进行绘制计算。
深入VSync机制
Vsync 信号一般是由硬件产生的,每个 Vsync 信号之间的时间,就是每一帧生产 / 消费的间隙。
Vsync 有两种,Vsync-app 和 Vsync-sf,前者用于告诉 Choreographer,是时候协调 app 生产buffer;后者用于告诉 SurfaceFlinger,是时候来消费buffer合成并显示到屏幕了
信号产生
Vsync 信号可以由硬件产生,也可以用软件模拟,不过现在基本上都是硬件产生,负责产生硬件 Vsync 的是 HWC,HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinge ,
消费
DispSync 将 Vsync 生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC_APP 和 VSYNC_SF 信号 消费如下
- VSYNC_APP——>Choreographer
- VSYNC_SF——>SurfaceFlinger
这里先简单介绍下Choreographer
Choreographer 的引入,主要是配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms,Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用
渲染层(App)与 Vsync 打交道的是 Choreographer, 而合成层与 Vsync 打交道的,则是 SurfaceFlinger。 SurfaceFlinger 也会在 Vsync 到来的时候,将所有已经准备好的 Surface 进行合成操作
下面结合图形数据流动方向继续深入介绍下: 概了解 Android 中的图形数据流的方向,从下面这张图,结合 Android 的图像流,我们大概把从 App 绘制到屏幕显示,分为下面几个阶段
-
第一阶段:App 在收到 Vsync-App 的时候,在主线程进行 measure、layout、draw(构建 DisplayList , 里面包含 OpenGL 渲染需要的命令及数据) 。这里对应的 Systrace 中的主线程 doFrame 操作
-
第二阶段:CPU 将数据上传(共享或者拷贝)给 GPU, 这里 ARM 设备 内存一般是 GPU 和 CPU 共享内存。这里对应的 Systrace 中的渲染线程的 flush drawing commands 操作
-
第三阶段:通知 GPU 渲染,真机一般不会阻塞等待 GPU 渲染结束,CPU 通知结束后就返回继续执行其他任务,使用 Fence 机制辅助 GPU CPU 进行同步操作
-
第四 阶段:swapBuffers,并通知 SurfaceFlinger 图层合成。这里对应的 Systrace 中的渲染线程的 eglSwapBuffersWithDamageKHR 操作
-
第五阶段:SurfaceFlinger 开始合成图层,如果之前提交的 GPU 渲染任务没结束,则等待 GPU 渲染完成,再合成(Fence 机制),合成依然是依赖 GPU,
不过这就是下一个任务了.这里对应的 Systrace 中的 SurfaceFlinger 主线程的 onMessageReceived 操作(包括 handleTransaction、handleMessageInvalidate、handleMessageRefresh)SurfaceFlinger 在合成的时候,会将一些合成工作委托给 Hardware Composer,从而降低来自 OpenGL 和 GPU 的负载,只有 Hardware Composer 无法处理的图层,或者指定用 OpenGL 处理的图层,其他的 图层偶会使用 Hardware Composer 进行合成
- 第六阶段 :最终合成好的数据放到屏幕对应的 Frame Buffer 中,固定刷新的时候就可以看到了
Vsync Offset
Vsync Offset 我们指的是 VSYNC_APP 和 VSYNC_SF 之间有一个 Offset
Vsync 信号可以由硬件产生,也可以用软件模拟,不过现在基本上都是硬件产生,负责产生硬件 Vsync 的是 HWC,HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinge , DispSync 将 Vsync 生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC_APP 和 VSYNC_SF 信号. 其中 app 和 sf 相对 hw_vsync_0 都有一个偏移,即 phase-app 和 phase-sf,如下图
在systrace感受Vsync
我们在日常开发中可以在systrace中了解下下图显示在 Systrace 中,SurfaceFlinger 进程中的 VSYNC_APP 和 VSYNC_SF 的情况