Android 屏幕画面生成的整体大致流程和一些相关概念:Window & Surface

#.  屏幕画面生成的 整体 大致流程
下面我们从Window开始说起,逐步讲到屏幕画面输出的大致流程:
    
    我们平时使用的Activity/Dialog/Toast中都包含一个Window对象,从概念上讲,这个Window是所有View的载体,它由PhoneWindow来实现,内部包含一个DecorView,而DecorView是View树的最顶层节点。
    但在app使用过程中,一个View我们能找到具体与什么相对应,而Window找不到,因为Window只是Android框架中的一个抽象概念,它为了便于理解和实现相关功能管理而出现,它对应的只是一个封装类,封装了相应的数据结构和方法。    
    一个Window对应着一个DecorView和一个ViewRootImpl,它是通过ViewRootImpl来对内部的View来进行各种功能管理的。
    每个Window都有一个显示层级z-ordered,层级大的Window画面会覆盖在层级小的Window上面。(可以想象Android屏幕有个z轴,方向垂直屏幕向外,每个Window的位置坐标不仅有x、y轴对应值决定其在平面上的偏移,还有z值决定画面位置的高、低)
    Window创建后,系统会为其分配一个Surface(其实是ViewRootImpl持有的),用于承载上面的画面。Surface中包含一个Canvas对象,用于具体绘制画面。每一轮的绘制过程,这个Canvas对象会按照View树的结构依次传递给相应View,每个View根据情况在自己的draw()/onDraw()方法中绘制自己对应区域的画面。最终这些画面构成了整个Window对应的Surface画面。
    而通过共享内存,系统底层服务SurfaceFlinger可以获取这些Surface的输出缓冲区数据。简单地将,SurfaceFlinger会把屏幕上这些Surface的画面,根据其显示层级、大小、位置、透明度等,经过一系列处理,合成一个最终画面,最终输出到屏幕缓冲区,在下一次屏幕刷新时显示在屏幕上。
    就像很多其它设备一样,Android手机屏幕也有一个刷新频率,一般为60Hz,即每秒刷新60次,每次会去屏幕的画面缓存区取数据然后显示在屏幕上。每一轮显示屏把其硬件帧缓冲区上的数据都扫描显示完后,就会发送一个垂直同步信号VSYNC,这个垂直同步信号是下一轮View重新绘制、SurfaceFlinger图层合成的触发必要条件。例如:调用TextView的setText()方法改变其内容后,屏幕并没有立即显示新的内容,而是等下一次屏幕刷新时才显示新内容,之所以我们没有察觉,是因为中间的时间太短了,只有0~16ms左右。但并非每一轮View都会调用draw()/onDraw()进行重绘,只有View的位置或内容发生改变,系统判断需要重绘时,才会重绘。当然也可以手动调用individual()或postIndividual()废弃当前的缓存画面,该情况下一轮 VSYNC到来时也会去重绘。
##.ViewRootImpl简介
    它不是View,但顾名思义,它就像一棵View树的根,管理着整个View树的相关功能,整个View树上的很多功能链条的起始点都在这里,例如measure()/layout()/draw()等最初都是由其触发的。
    View树最顶层的View是DecorView。
##.SurfaceFlinger简介
    SurfaceFlinger是Android显示相关的一个重要native层服务进程,负责为Surface分配图形缓存区、消费各Surface输出的绘制数据并合成一张画面,并将合成后的画面数据输送到更底层,最终显示到屏幕上。
    各个在Surface上进行绘制的App与SurfaceFlinger之间是生产者-消费者关系,各个app负责在Surface上生产画面,SurfaceFlinger负责消费这些画面并将它们合成一个画面。
##.Surface
     是图形缓冲区(GraphicBuffer)的封装类,一般用作图像绘制的载体,例如用于承载View画面、承载SurfaceView画面、承载相机拍摄的图像、或者承载MediaCodec的输入/输出画面、承载ImageReader输入画面等等。
     Surface都是双缓冲的,从概念上讲,可以简化理解为有两个缓冲区引用,一个frontBuffer和一个backBuffer,backBuffer指向后置缓冲区,用于缓存正在绘制的画面;而frontBuffer指向前置缓冲区,用于缓存最近绘制完毕、要提交使用的画面。  

绘制中不断地循环这个过程:

    1.执行lockCanvas()后,获取Surface的Canvas对象,用于在backBuffer上进行绘制;

    2.在backBuffer上绘制完毕后,调用unlockCanvasAndPost(),互换二者身份,原来的backBuffer变为frontBuffer用于提交给外部使用,而原来的frontBuffer变为backBuffer去参与下一轮绘制。

    这里的“外部使用”所指代的,具体要看画面消费者是谁,例如当SurfaceFinger消费画面时,会被用于合成屏幕画面,最终输出到屏幕上;当MediaCodec编码器消费画面时,会被用于编码器编码,然后输出到编码器输出缓冲区。

        
(大致过程可以这么理解,其实底层在利用BufferQueue来进行Buffer的分配和循环使用,具体细节跟上面会有一些差异,但一般情况下画面生产方速度不会快于画面消费方,BufferQueue中一般只有两个GraphicBuffer,效果的确是前后缓冲区互换索引。)
    
    其中lockCanvas(脏区)时可以传入一个脏区,这样在lockCanvas()中会把frontBuffer中“上一轮脏区-本轮脏区”那部分画面复制到backBuffer上,而绘制时只需要绘制本轮脏区范围对应的内容,没必要全部重新绘制。如果lockCanvas(null),则意味着本轮整个backBuffer范围都是脏区。这是Surface双缓冲特性的一个重要应用。
    不过Surface双缓冲设计的主要作用,应该是当Surface的画面生产方 与 画面消费方 异步工作时,始终能提供一个图形缓冲区(backBuffer)供生产方绘制,同时始终能提供一个图形缓冲区(frontBuffer)存储完整的画面供消费方消费。
 (如果是同步工作的话,其实一个缓冲区就能胜任工作流程,让生产方先在缓冲区上绘制完,然后提交给消费者消费,然后继续循环利用缓冲区就行了。)
    
    
##.View和SurfaceView的绘制
    普通View绘制时,draw()/onDraw()获取到的Canvas都是从上层View树中传递过来的,而这个Canvas最初也是从Window对应的Surface中获取的。整个Window上的所有View都是在Surface的backBuffer上绘制。
    至于SurfaceView,本身就直接管理一个独立的Surface,同时SurfaceView又属于某个View树,附在对应的Window上,所以它与两个Surface相关联。SurfaceView自己独自的Surface的显示层级较低,而View树所在Window的显示层级较高,所以前者在后者的下面。
    SurfaceView真实画面绘制在自己独自拥有的Surface上,而这张Surface位于Window下面,是如何显示出来的呢?
    因为Window上的View树绘制时,SurfaceView也会参与,它会把Window的Surface上自己对应的区域绘制成透明色,于是Window下面SurfaceView独立的Surface就可以显示出来了。
    无论是普通View,还是SurfaceView,它们都是在Surface上绘制,差别是Window上的多个View共享一张Surface,而SurfaceView独享一张Surface。
    Surface是双缓冲的,所以SurfaceView的绘制流程是双缓冲的,它在绘制时也可以利用lockCanvas(脏区)从frontBuffer往backBuffer上复制部分内容,每轮不一定要全部重新绘制一遍,只需要把画面变化部分指定为脏区(传null时为整个backBuffer范围),绘制本轮脏区范围的内容。
    对于整个Window而言,它对应整张Surface,绘制时也可以如此,每次只绘制画面有变化的局部内容,把涉及到的View重绘一遍。从宏观上看,它的整体绘制流程也可以说是有双缓冲的。
    但对于Window上单个普通View,它并不直接跟Surface打交道,它只是从View树上层接受传递来的Canvas,如果需要就进行自己的draw()/onDraw()绘制逻辑,在指定好的区域内用Canvas绘制自己的图像。单个View的绘制只是Surface上backBuffer绘制流程的一个子环节,这个子环节没有双缓冲。在View提供的绘制机制中,无法指定一个脏区只绘制View的局部内容,每次都需要完全重新绘制一遍。此即网上经常提及的“普通View没有双缓冲”说法。
    普通View的绘制机制没有提供双缓冲,是因为正常情况下它承担的角色没这种需求。当有这种需求时,例如要实现类似画板的功能,要实现局部绘制的能力,可以自定义View,新建一个Bitmap作为额外的缓冲,每次在这个Bitmap上绘制要修改的部分,然后再把整个Bitmap绘制到View上。

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124651710