1.1.Project Butter,ui绘制原理,动画原理


Android Project Butter分析

Project Butter(黄油计划)的特性,包括:

  1. 通过Vsync信号来同步UI绘制和动画,使得它们可以获得一个达到60fps的固定的帧率;

    B. 三缓冲支持,改善GPU和CPU之间绘制节奏不一致的问题;

    C. 将用户输入,例如touch event,同步到下一个Vsync信号到来时再处理;

    D. 预测用户的touch行为,以获得更好的交互响应;

    E. 每次用户touch屏幕时,进行CPU Input Boost,以便减少处理延时。

    一背景知识介绍

    随着时间的推移,Android OS系统一直在不断进化、壮大,日趋完善。但直到Android 4.0问世,有关UI显示不流畅的问题也一直未得到根本解决。在整个进化过程中,Android在Display(显示)系统这块也下了不少功夫,例如,使用硬件加速等技术,但本质原因似乎和硬件关系并不大,因为iPhone的硬件配置并不比那些价格相近的Android机器的硬件配置强,而iPhone UI的流畅性强却是有目共睹的。

    从Android 4.1(版本代号为Jelly Bean)开始,Android OS开发团队便力图在每个版本中解决一个重要问题(这是不是也意味着Android OS在经过几轮大规模改善后,开始进入手术刀式的精加工阶段呢?)。作为严重影响Android口碑问题之一的UI流畅性差的问题,首先在Android 4.1版本中得到了有效处理。其解决方法就是本文要介绍的Project Butter。

    Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer([?k?r?'?ɡr?f?(r)] 编舞者)。其中,VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种在PC上已经很早就广泛使用的技术。读者可简单的把它认为是一种定时中断。

    接下来,本文将围绕VSYNC来介绍Android Display系统的工作方式。请注意,后续讨论将以Display为基准,将其划分成16ms长度的时间段,在每一时间段中,Display显示一帧数据(相当于每秒60帧)。时间段从1开始编号。

    首先是没有VSYNC的情况,如图1所示:

    1  没有VSYNC的绘图过程

    由图1可知:

  •  时间从0开始,进入第一个16ms:Display显示第0帧,CPU处理完第一帧后,GPU紧接其后处理继续第一帧。三者互不干扰,一切正常。
  • 时间进入第二个16ms:因为早在上一个16ms时间内,第1帧已经由CPU,GPU处理完毕。故Display可以直接显示第1帧。显示没有问题。但在本16ms期间,CPU和GPU却并未及时去绘制第2帧数据(注意前面的空白区),而是在本周期快结束时,CPU/GPU才去处理第2帧数据。
  • 时间进入第3个16ms,此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次(对应时间段上标注了一个Jank)。

通过上述分析可知,此处发生Jank的关键问题在于,为何第1个16ms段内,CPU/GPU没有及时处理第2帧数据?原因很简单,CPU可能是在忙别的事情(比如某个应用通过sleep固定时间来实现动画的逐帧显示),不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了!

为解决这个问题,Project Butter引入了VSYNC,这类似于时钟中断。结果如图2所示:

2  引入VSYNC的绘制过程

由图2可知,每收到VSYNC中断,CPU就开始处理各帧数据。整个过程非常完美。

不过,仔细琢磨图2却会发现一个新问题:图2中,CPU和GPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPU的FPS(帧率,Frames Per Second)要高于Display的FPS。确实如此。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与Display的FPS相同。但这种处理并没有什么问题,因为Android设备的Display FPS一般是60,其对应的显示效果非常平滑。

如果CPU/GPU的FPS小于Display的FPS,会是什么情况呢?请看图3:

3  CPU/GPU FPS较小的情况

由图3可知:

  • 在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。
  • 同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦过了VSYNC时间点,CPU就不能被触发以处理绘制工作了。

为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer。如果有第三个Buffer的存在,CPU就能直接使用它,而不至于空闲。出于这一思路就引出了Triple Buffer。结果如图4所示:

4  Triple Buffer的情况

由图4可知:

  • 第二个16ms时间段,CPU使用C Buffer绘图。虽然还是会多显示A帧一次,但后续显示就比较顺畅了。

是不是Buffer越多越好呢?回答是否定的。由图4可知,在第二个时间段内,CPU绘制的第C帧数据要到第四个16ms才能显示,这比双Buffer情况多了16ms延迟。所以,Buffer最好还是两个,三个足矣。

介绍了上述背景知识后,下文将分析Android Project Buffer的一些细节。

[②],并未做什么改动。

以上是Project Buffer对SurfaceFlinger所做的一些改动。那么Triple Buffer是怎么处理的呢?幸好从Android 2.2开始,Display的Page Flip算法就不依赖Buffer的个数,Buffer个数不过是算法的一个参数罢了。所以,Triple Buffer的引入,只是把Buffer的数目改成了3,而算法本身相对于4.0来说并没有变化。图7为Triple Buffer的设置示意图:

7  Layer.cpp中对Triple Buffer的设置

图7所示,为Layer.cpp中对Buffer个数的设置。TARGET_DISABLE_TRIPLE_BUFFERING宏可设置Buffer的个数。对某些内存/显存并不是很大的设备,也可以选择不使用Triple Buffer。

Java类。第一次看到这个词时,我很激动。一个小小的命名真的反应出了设计者除coding之外的广博的视界。试想,如果不是对舞蹈有相当了解或喜爱,一般人很难想到用这个词来描述它。

Choreographer的定义和基本结构如图8所示:

8  Choreographer的定义和结构

图8中:

  • Choreographer是线程单例的,而且必须要和一个Looper绑定,因为其内部有一个Handler需要和Looper绑定。
  • DisplayEventReceiver是一个abstract class,其JNI的代码部分会创建一个IDisplayEventConnection的VSYNC监听者对象。这样,来自EventThread的VSYNC中断信号就可以传递给Choreographer对象了。由图8可知,当VSYNC信号到来时,DisplayEventReceiver的onVsync函数将被调用。
  • 另外,DisplayEventReceiver还有一个scheduleVsync函数。当应用需要绘制UI时,将首先申请一次VSYNC中断,然后再在中断处理的onVsync函数去进行绘制。
  • Choreographer定义了一个FrameCallback interface,每当VSYNC到来时,其doFrame函数将被调用。这个接口对Android Animation的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。

Choreographer的主要功能是,当收到VSYNC信号时,去调用使用者通过postCallback设置的回调函数。目前一共定义了三种类型的回调,它们分别是:

  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和Animation的处理有关。
  • CALLBACK_TRAVERSAL:优先级最低,Handles layout and draw. Runs after all other asynchronous messages have been handled。
  • CALLBACK_COMMIT:Handles post-draw operations for the frame. Runs after traversal completes, 遍历完成的提交操作,用来修正动画启动时间。

优先级高低和处理顺序有关。当收到VSYNC中断时,Choreographer将首先处理INPUT类型的回调,然后是ANIMATION类型,最后才是TRAVERSAL类型。

此外,读者在自行阅读Choreographer相关代码时,还会发现Android对Message/Looper类也进行了一番小改造,使之支持Asynchronous Message和Synchronization Barrier(参照Looper.java的postSyncBarrier函数)。其实现非常巧妙,这部分内容就留给读者自己理解并欣赏了。

相比SurfaceFlinger,Choreographer是Android 4.1中的新事物,下面将通过一个实例来简单介绍Choreographer的工作原理。

假如UI中有一个控件invalidate了,那么它将触发ViewRootImpl的invalidate函数,该函数将最终调用ViewRootImpl的scheduleTraversals。其代码如图9所示:

9  ViewRootImpl scheduleTraversals函数的实现

由图9可知,scheduleTraversals首先禁止了后续的消息处理功能,这是由设置Looper的postSyncBarrier来完成的。一旦设置了SyncBarrier,所有非Asynchronous的消息便将停止派发。

然后,为Choreographer设置了CALLBACK类型为TRAVERSAL的处理对象,即mTraversalRunnable。

最后调用scheduleConsumeBatchedInput,这个函数将为Choreographer设置了CALLBACK类型为INPUT的处理对象。

Choreographer的postCallback函数将会申请一次VSYNC中断(通过调用DisplayEventReceiver的scheduleVsync实现)。当VSYNC信号到达时,Choreographer doFrame函数被调用,内部代码会触发回调处理。代码片段如图10所示:

10  Choreographer doFrame函数片段

对ViewRootImpl来说,其TRAVERSAL回调对应的处理对象,就是前面介绍的mTraversalRunnable,它的代码很简单,如图11所示:

11  mTraversalRunnable的实现

doTraversal内部实现和Android 4.0版本一致。故相比于4.0来说,4.1只是把doTraversal调用位置放到VSYNC中断处理中了。

通过上边的介绍,可知Choreographer确实做到了对绘制工作的统一安排,不愧是个长于统筹安排的"舞蹈编导"。

https://www.jianshu.com/p/9b14bf15ddaf