android triple buffer

转自:http://blog.csdn.net/yangwen123/article/details/16344375

也可参考:http://djt.qq.com/article/view/987


进程间通讯机制

    Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信,如图所示:

Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,因此,它们采用某种进程间通信机制来进行通信。由于Android应用程序在通知SurfaceFlinger服务来绘制自己的UI的时候,需要将UI数据传递给SurfaceFlinger服务,例如,要绘制UI的区域、位置等信息。一个Android应用程序可能会有很多个窗口,而每一个窗口都有自己的UI数据,因此,Android系统的匿名共享内存机制就派上用场了。

每一个Android应用程序与SurfaceFlinger服务之间,都会通过一块匿名共享内存来传递UI数据,如下所示:

 

 

但是单纯的匿名共享内存在传递多个窗口数据时缺乏有效的管理,所以匿名共享内存就被抽象为一个更上流的数据结构SharedClient,如下图所示:

 

在每个SharedClient中,最多有31个SharedBufferStack,每个SharedBufferStack都对应一个Surface,即一个窗口。这样,我们就可以知道为什么每一个SharedClient里面包含的是一系列SharedBufferStack而不是单个SharedBufferStack:一个SharedClient对应一个Android应用程序,而一个Android应用程序可能包含有多个窗口,即Surface。从这里也可以看出,一个Android应用程序至多可以包含31个窗口。

每个SharedBufferStack中又包含了N个缓冲区(<4.1 N=2; >=4.1 N=3),即显示刷新机制中即将提到的双缓冲和三重缓冲技术。

显示刷新机制

    一般我们在绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区(SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。理想情况下,这样一个刷新会在16ms内完成(60FPS),下图就是描述的这样一个刷新过程(Display处理前Front Buffer,CPU、GPU处理Back Buffer。


随着时间的推移,Android OS系统一直在不断完善。但直到Android 4.0问世,有关UI显示不流畅的问题也一直未得到根本解决。在整个进化过程中,Android在显示系统这块也下了不少功夫,例如,使用硬件加速等技术,但本质原因似乎和硬件关系并不大,因为iPhone的硬件配置并不比那些价格相近的Android机器的硬件配置强,而iPhone UI的流畅性强却是有目共睹的。从Android 4.1(版本代号为Jelly Bean)开始,Android OS开发团队便力图在每个版本中解决一个重要问题。作为严重影响Android口碑问题之一的UI流畅性差的问题,首先在Android 4.1版本中得到了有效处理。其解决方法就是本文要介绍的Project Butter。Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。其中,VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种在PC上已经很早就广泛使用的技术。读者可简单的把它认为是一种定时中断。接下来,本文将围绕VSYNC来介绍Android Display系统的工作方式。

Android系统UI交互滞后问题


在一个典型的显示系统中,frame buffer代表了屏幕即将要显示的一帧画面。假如CPU/GPU绘图过程与屏幕刷新所使用的buffer是同一块,那么当它们的速度不同步的时候,是很可能出现类似的画面“割裂”的。举个具体的例子来说,假设显示器的刷新率为66Hz,而CPU/GPU绘图能力则达到100Hz,也就是它们处理完成一帧数据分别需要0.015秒和0.01秒。

0.01秒时,由于两者速率相差不小,此时buffer中已经准备好了第1帧数据,显示器只显示了第1帧画面的2/3
0.015秒时,第1帧画面完整地显示出来了,此时buffer中有1/3的部分已经被填充上第2帧数据了
0.02秒时,Buffer中已经准备好第2帧数据,而显示屏出现了screen tearing,有三分之一是第2帧内容,其余的则属于第1帧画面

在单缓冲区的情况下,这个问题很难规避。所以引进了双缓冲技术,基本原理就是采用两块buffer。一块back buffer用于CPU/GPU后台绘制,另一块framebuffer则用于显示,当back buffer准备就绪后,它们才进行交换。那么什么时候切换两个缓冲区最合适呢?显示器有两个重要特性,行频和场频。行频(Horizontal ScanningFrequency)又称为“水平扫描频率”,是屏幕每秒钟从左至右扫描的次数; 场频(Vertical Scanning Frequency)也称为“垂直扫描频率”,是每秒钟整个屏幕刷新的次数。由此也可以得出它们的关系:
行频=场频*纵坐标分辨率。
当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI)。这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现screentearing的状况。VSync是VerticalSynchronization的简写,它利用VBI时期出现的vertical sync pulse来保证双缓冲在最佳时间点才进行交换。在没有VSync信号同步下的绘图过程:
没有VSYNC的绘图过程

  •  时间从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帧数据,时间又错过了!

为解决这个问题,从Android 4.1Jelly Bean开始,Project Buffer引入了VSYNC,系统在收到VSync pulse后,将马上开始下一帧的渲染。结果如下图所示:

引入VSYNC的绘制过程

每收到VSYNC中断,CPU就开始处理各帧数据。大部分的Android显示设备刷新率是60Hz,这也就意味着每一帧最多只能有1/60=16ms左右的准备时间。假如CPU/GPU的FPS(FramesPer Second)高于这个值,那么显示效果将很好。但是,这时出现一个新问题:CPU和GPU处理数据的速度都能在16ms内完成,而且还有时间空余,但必须等到VSYNC信号到来后才处理下一帧数据,因此CPU/GPU的FPS被拉低到与Display的FPS相同。下图是采用双缓冲区的显示效果:

双缓冲下CPU/GPU FPS大于刷新频率

同时采用了双缓冲技术以及VSYNC,可以看到整个过程还是相当不错的,虽然CPU/GPU处理所用的时间时短时长,但总的来说都在16ms以内,因而不影响显示效果。A和B分别代表两个缓冲区,它们不断地交换来正确显示画面。如果CPU/GPU的FPS小于Display的FPS,会是什么情况呢?


双缓冲下CPU/GPU FPS小于刷新频率

当CPU/GPU的处理时间超过16ms时,第一个VSync到来时,缓冲区B中的数据还没有准备好,于是只能继续显示之前A缓冲区中的内容。而B完成后,又因为缺乏VSync信号,CPU/GPU只能等待下一个VSync的来临才开处理下一帧数据。于是在这一过程中,有一大段时间是被浪费。当下一个VSync出现时,CPU/GPU马上执行操作,此时它可操作的buffer是A,相应的显示屏对应的就是B。这时看起来就是正常的。只不过由于执行时间仍然超过16ms,导致下一次应该执行的缓冲区交换又被推迟了。也就是说在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示,同时CPU无所事事,因为A 被Display在使用。B被GPU在使用。为什么CPU不能在第二个16ms处即VSync到来就开始工作呢?原因就是只有两个Buffer。如果有第三个Buffer的存在,CPU就可以开始工作,而不至于空闲。出于这一思路就引出了Triple Buffer。结果如图所示:


Triple Buffering

第二个16ms时间段,CPU使用C Buffer绘图。虽然刚开始还是会多显示A帧一次,但后续显示效果就比较好,第三个VSync信号到来时,由于GPU/CPU都处理完了B,因此B被显示,在第四个VSync信号到来时,GPU/CPU同时完成了A和C,并着手开始处理B,此时C被显示。从上图可以看出,CPU绘制的第C帧数据要到第四个16ms才能显示,这比双Buffer情况多了16ms延迟。我们知道,应用程序这边的本地窗口Surface在SurfaceFlinger服务进程端有一个对应的BufferQueue对象,该对象用于管理Surface的图形绘制缓冲区。BufferQueue中最多有32个BufferSlot,不过在实际使用时具体值是可以设置的。在Layer对象的onFirstRef函数中初始化了图形缓冲区的个数:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #ifdef TARGET_DISABLE_TRIPLE_BUFFERING  
  2. #warning "disabling triple buffering"  
  3.     mSurfaceTexture->setBufferCountServer(2);  
  4. #else  
  5.     mSurfaceTexture->setBufferCountServer(3);  
  6. #endif  

Project Buffer分析


Project Buffer的三个关键点:
1. 需要VSYNC定时中断;
2. 当双Buffer不够使用时,该系统可分配第三块Buffer;
3. 图形buffer的绘制工作又VSYNC信号触发;


  • HardwareComposer封装了相关的HAL层,如果硬件厂商提供的HAL层实现能定时产生VSYNC中断,则直接使用硬件的VSYNC中断,否则HardwareComposer内部会通过VSyncThread来模拟产生VSYNC中断(其实现很简单,就是sleep固定时间,然后唤醒)。
  • 当VSYNC中断产生时(不管是硬件产生还是VSyncThread模拟的),VSyncHandler的onVSyncReceived函数将被调用。所以,对VSYNC中断来说,VSyncHandler的onVSyncReceived,就是其中断处理函数。
VSyncHandler的实例是EventThread。下边是EventThread类的声明:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class EventThread : public Thread, public DisplayHardware::VSyncHandler  
EventThread本身运行在一个单独的线程中,并继承了VSyncHandler。EventThread在其线程函数threadLoop中等待下一次VSYNC的到来,并派发该中断事件给VSYNC监听者。通过EventThread,VSYNC中断事件可派发给多个该中断的监听者去处理。


IDisplayEventConnection是一个纯虚类,它代表VSYNC中断的监听者。其实体类是EventThread的内部类Connection。IDisplayEventConnection定义了一个getDataChannel函数,该函数返回一个BitTube实例,其内部实现为socketpair。这个实例提供的read/write方法,用于传送具体的信号数据。EventThread最重要的一个VSYNC监听者就是MessageQueue的mEvents对象,来自EventThread的VSYNC中断信号,将通过MessageQueue转化为一个REFRESH消息并传递给SurfaceFlinger的onMessageReceived函数处理。DisplayEventReceiver是一个abstract class,其JNI的代码部分会创建一个IDisplayEventConnection的VSYNC监听者对象。这样,来自EventThread的VSYNC中断信号就可以传递给Choreographer对象了。当Choreographer收到VSYNC信号时,就会调用使用者通过postCallback设置的回调函数。



  Project Buffer分析

上一节对VSYNC进行了理论分析,其实也引出了Project Buffer的三个关键点:

  • 核心关键:需要VSYNC定时中断。
  • Triple Buffer:当双Buffer不够使用时,该系统可分配第三块Buffer
  • 另外,还有一个非常隐秘的关键点:即将绘制工作都统一到VSYNC时间点上。这就是Choreographer的作用。Choreographer是一个极富诗意的词,意为舞蹈编导。在它的统一指挥下,应用的绘制工作都将变得井井有条。

下面来看Project Buffer实现的细节。

2.1  SurfaceFlinger家族的改进

首先被动刀的是SurfaceFlinger家族成员。目标是提供VSYNC中断。相关类图如图5所示:

5  SurfaceFlinger中和VSYNC有关的类

由图5可知:

  • HardwareComposer封装了相关的HAL层,如果硬件厂商提供的HAL层实现能定时产生VSYNC中断,则直接使用硬件的VSYNC中断,否则HardwareComposer内部会通过VSyncThread来模拟产生VSYNC中断(其实现很简单,就是sleep固定时间,然后唤醒)。
  • VSYNC中断产生时(不管是硬件产生还是VSyncThread模拟的),VSyncHandleronVSyncReceived函数将被调用。所以,对VSYNC中断来说,VSyncHandleronVSyncReceived,就是其中断处理函数。

SurfaceFlinger家族中,VSyncHandler的实例是EventThread。下边是EventThread类的声明:

class EventThread : public Thread, public DisplayHardware::VSyncHandler

EventThread定义可知,它本身运行在一个单独的线程中,并继承了VSyncHandlerEventThread的核心处理在其线程函数threadLoop中完成,其处理逻辑主要是:

  • 等待下一次VSYNC的到来,并派发该中断事件给VSYNC监听者。

通过EventThreadVSYNC中断事件可派发给多个该中断的监听者去处理。相关类如图6所示:

6  EventThreadVSYNC中断监听者

由图6可知:

  • SurfaceFlingerThread派生,其核心功能单独运行在一个线程中。
  • SurfaceFlinger包括一个EventThread和一个MessageQueue对象。
  • 对象通过mEvents成员指向一个IDisplayEventConnection类型的对象。IDisplayEventConnection是一个纯虚类,它代表VSYNC中断的监听者。其实体类是EventThread的内部类Connection
  • IDisplayEventConnection定义了一个getDataChannel函数,该函数返回一个BitTube实例。这个实例提供的read/write方法,用于传送具体的信号数据(其内部实现为socketpair,可通过Binder实现进程跨越)。

EventThread最重要的一个VSYNC监听者就是MessageQueuemEvents对象。当然,这一切都是为最大的后台老板SurfaceFlinger服务的。来自EventThreadVSYNC中断信号,将通过MessageQueue转化为一个REFRESH消息并传递给SurfaceFlingeronMessageReceived函数处理。

有必要指出,4.1SurfaceFlinger onMessageReceived函数的实现仅仅是将4.0版本的SurfaceFlinger的核心函数挪过来罢了[②],并未做什么改动。

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

7  Layer.cpp中对Triple Buffer的设置

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

2.2  Choreographer介绍

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

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

8  Choreographer的定义和结构

8中:

  • Choreographer是线程单例的,而且必须要和一个Looper绑定,因为其内部有一个Handler需要和Looper绑定。
  • DisplayEventReceiver是一个abstract class,其JNI的代码部分会创建一个IDisplayEventConnectionVSYNC监听者对象。这样,来自EventThreadVSYNC中断信号就可以传递给Choreographer对象了。由图8可知,当VSYNC信号到来时,DisplayEventReceiveronVsync函数将被调用。
  • 另外,DisplayEventReceiver还有一个scheduleVsync函数。当应用需要绘制UI时,将首先申请一次VSYNC中断,然后再在中断处理的onVsync函数去进行绘制。
  • Choreographer定义了一个FrameCallback interface,每当VSYNC到来时,其doFrame函数将被调用。这个接口对Android Animation的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。
  • Choreographer的主要功能是,当收到VSYNC信号时,去调用使用者通过postCallback设置的回调函数。目前一共定义了三种类型的回调,它们分别是:
  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和Animation的处理有关。
  • CALLBACK_TRAVERSAL:优先级最低,和UI等控件绘制有关。

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

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

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

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

9  ViewRootImpl scheduleTraversals函数的实现

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

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

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

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

10  Choreographer doFrame函数片段

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

11  mTraversalRunnable的实现

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

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

三总结

本文通过对Android Project Butter的分析,向读者介绍了VSYNC原理以及Android Display系统的实现。除了VSYNC外,Project Butter还包括其他一些细节的改进,例如避免重叠区域的绘制等。

简言之,Project Butter从本质上解决了Android UI不流畅的问题,而且从Google I/O给出的视频来看,其效果相当不错。但实际上它对硬件配置还是有一定要求的。因为VSYNC中断处理的线程优先级一定要高,否则EventThread接收到VSYNC中断,却不能及时去处理,那就丧失同步的意义了。所以,笔者估计目前将有一大批单核甚至双核机器无法尝到Jelly Bean了。



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值