image
如图,当帧率小于刷新率时,比如图中的30FPS < 60HZ,就会出现相邻两帧看到的是同一个画面,这就造成了卡顿。这就是为什么我们总会说,要尽量保证一帧画面能够在16ms内绘制完成,就是为了和屏幕的刷新率保持同步。
下面将会介绍Android是如何来确保刷新率和帧率保持同步的。
Vsync(垂直同步)是什么?你可能在游戏的设置中见过Vsync,开启它通常能够提高游戏性能。在Android中,同样使用Vsync垂直同步来提高显示性能。它能够使帧率FrameRate和硬件的RefreshRate刷新强制保持一致。
HWComposer与Vsync不得不说的事看图啦看图啦。首先在最左边我们看到有个叫HWComposer的类,这是一个c++编写的类。它Android系统初始化时就被创建,然后开始配合硬件产生Vsync信号,也就是图中的HW_Vsync信号。当然它不是一直不停的在产生,这样会导致Vsync信号的接收者不停的接收到绘制、渲染命令,即使它们并不需要,这样会带来严重的性能损耗,因为进行了很多无用的绘制。所以它被设计设计成能够唤醒和睡眠的。这使得HWComposer在需要时才产生Vsync信号(比如当屏幕上的内容需要改变时),不需要时进入睡眠状态(比如当屏幕上的内容保持不变时,此时屏幕每次刷新都是显示缓冲区里没发生变化的内容)。
如图,Vsync的两个接收者,一个是SurfaceFlinger(负责合成各个Surface),一个是Choreographer(负责控制视图的绘制)。我们稍后再介绍,现在先知道它们是干什么的就行了。
Vsync offset机制为了提高效率,尽量减少卡顿,在Android 4.1时引入了Vsync机制,并在随后的4.4版本中加入Vsync offset偏移机制。
image
图1. 为4.1时期的Vsync机制。可以看到,当一个Vsync信号到来时,SurfaceFlinger和UI绘制进程会同时启动,导致它们竞争CPU资源,而CPU分配资源会耗费时间,着降低系统性能。同时当收到一个Vsync信号时,第N帧开始绘制。等再收到一个Vsync信号时,第N帧才被SurfaceFlinger合成。而需要显示到屏幕上,需要等都第三个Vsync信号。这是比较低效率。于是才有了图2. 4.4版本加入的Vsync offset机制。
图2. Google加入Vsync offset机制后,原本的HW_Vsync信号会经过DispSync会分成Vsync和SF_Vsync两个虚拟化的Vsync信号。其中Vsync信号会发送到Choreographer中,而SF_Vsync会发送到SurfaceFlinger中。理论上只要phase_app和phase_sf这两个偏移参数设置合理,在绘制阶段消耗的时间控制好,那么画面就会像图2中的前几帧那样有序流畅的进行。理想总是美好的。实际上很难一直维持这种有序和流畅,比如frame_3是比较复杂的一帧,它的绘制完成的时间超过了SurfaceFlinger开始合成的时间,所以它必须要等到下一个Vsync信号到来时才能被合成。这样便造成了一帧的丢失。但即使是这样,如你所见,加入了Vsync offset机制后,绘制效率还是提高了很多。
从图中可以看到,Vsync和SF_Vsync的偏移量分别由phase_app和phase_sf控制,这两个值是可以调节的,默认为0,可为负值。你只需要找到BoardConfig.mk文件,就可以对这两个值进行调节。
回到ViewRootImpl前面介绍了几个关键的概念,现在我们回到ViewRootImpl中去,在图中找到ViewRootImpl的对应位置。
前面说过,ViewRootImpl控制着一个Window中的整个视图树的绘制。那它是如何进行控制的呢?一次绘制究竟是如何开始的呢?
image
在ViewRootImpl创建的时候,会获取到前面提到过过的一个关键对象Choreographer。Choreographer在一个线程中仅存在一个实例,因此在UI线程只有一个Choreographer存在。也就说,通常情况下,它相当于一个应用中的单例。
在ViewRootImpl初始化时,会实现一个Choreographer.FrameCallback(这是一个Choreographer中的内部类),并向Choreographer中post。顾名思义,FrameCallback会在每次接收到Vsync信号时被回调。
Choreographer.java
FrameCallback一旦被注册,那么每次收到Vsync信号时它都会被回调。利用它,我们可以实现会帧率的监听。