Android图形架构总览

图形渲染流程

​ 在Android中,所有的渲染都是将内容绘制到对应的Surface上,Surface作为BufferQueue的生产方,每当发生入队操作时,都会通知BufferQueue创建者进行消费,也就是SurfaceFlinger,由SurfaceFlinger进行合并,然后发送到显示器进行显示。

下面这张图描述了关键组件的工作流程:

工作流程

  1. 当前一个可见的界面被创建时,会通过WindowManager去创建一个Window对象,而每一个Window对象中都会包含一个Surface对象,该对象由WindowManager向SurfaceFlinger请求创建。Surface中包含一个BufferQueue,它由SurfaceFlinger向HAL层调用Gralloc对其进行内存分配。

  2. 应用获取到该Window对象后,通过Surface对象创建EGLSurface对象,然后在View的draw方法中使用Canvas进行绘制,此时使用的Canvas是一个OpenGL ES Canvas,也就是会通过OpenGL ES对View进行绘制,GLES渲染完成后,通过EGL的swapBuffers方法将绘制的缓冲区加入到EGLSurface连接的Surface所拥有的BufferQueue上。

  3. SurfaceFlinger接收HAL层发送来的VSYNC事件进行刷新,SurfaceFlinger从每个Surface的BufferQueue中获取新的缓冲区,释放掉其之前的缓冲区,如果获取不到新的,则继续使用之前的缓冲区。

  4. 将所有要显示的Surface传入到HAL层进行查询,哪些需要进行合并,然后根据返回的标记,对需要进行合并的Surface进行合并(使用OpenGL ES将两个缓冲区合并到第三个缓冲区中),然后将处理后的所有Surface交给显示器,由显示器进行叠加层显示。参考下图

合并流程


BufferQueue

​ BufferQueue用来连接缓冲区生产方和缓冲区消费方。

​ 生产方向BufferQueue请求一个可用的缓冲区(dequeueBuffer),然后向该缓冲区中写入数据后将其返回到队列中(queueBuffer),消费方从队列中获取该缓冲区并使用它(acquireBuffer),消费方使用完毕后将该缓冲区释放并重新返回到队列中(releaseBuffer)。


SurfaceFlinger

​ SurfaceFlinger负责创建Surface,并消费其创建的Surface。应用想要显示一个界面,都必须通过WindowManager向SurfaceFlinger请求一个Surface,然后将显示的内容交给Surface传递给SurfaceFlinger。

​ SurfaceFlinger仅仅在显示器刷新的时候才会进行处理,而不是应用更新了Surface中的缓冲队列就立马处理,这样可以减少内存的使用。显示器的刷新是通过VSYNC信号发送到SurfaceFlinger上的。

​ 当SurfaceFlinger接收到VSYNC信号,SurfaceFlinger会遍历所有可见Surface,获取每个Surface最新的缓冲区,如果获取到了释放之前的缓冲区,如果没有获取到最新的缓冲区,则继续使用之前的缓冲区。如果该Surface自始至终都没有过缓冲区,则忽略该Surface。

​ SurfaceFlinger收集了所有可见Surface的最新缓冲区后,会询问HWC层硬件将如果处理合成,然后将HWC层标记为SurfaceFlinger合成的全部合成为一个缓冲区,最后将剩下的所有缓冲区传递给HWC层进行显示。


WindowManager

​ WindowManager控制Window对象,Window是View对象的容器。WindowManager监视生命周期,输入和焦点事件,屏幕方向,过渡,动画,位置,变换,z顺序以及窗口的许多其他方面。WindowManager将所有窗口元数据发送到SurfaceFlinger,以便SurfaceFlinger可以使用该数据进行合成、显示。


屏幕叠加层

​ 上面说到过SurfaceFlinger在收集了所有可见Surface的最新缓冲区后,需要询问HWC层,那是因为显示器一般都有叠加层支持,也就是显示器并不是只能显示一个缓冲区,它可以同时显示多个缓冲区,可以从不同的缓冲区读取屏幕上不同位置的数据。这样做可以非常显著的提高效率,避免了SurfaceFlinger在不停的合并(SurfaceFlinger的合并是通过GPU来合成,这会抢占应用使用GPU渲染的时间),只有在Surface的个数大于叠加层的个数的时候才会由SurfaceFlinger来合并。

所以SurfaceFlinger维护的Surface的数量会对能耗和性能产生很大影响。

叠加层最少有4个:

  • 状态栏
  • 系统导航栏
  • 应用
  • 壁纸

Surface

​ Surface是一个接口,维护了一个对BufferQueue的连接,用于将缓冲区从生产方流动都消费方。

向Surface中绘制有两种方式:

GPU渲染

一般使用OpenGL ES或者Vulkan渲染,View的draw方法使用的就是这种方式

CPU渲染

使用lockCanvas或者new Canvas,这种方式都是使用CPU渲染

使用lockCanvas时需要注意,一旦使用CPU渲染的Canvas向Surface中进行了绘制,那么CPU渲染程序会连接到Surface中的BufferQueue上,直到Surface被销毁才会断开连接,因为连接无法断开。所以此后该Surface将无法接受GPU渲染程序的连接。(新版本中使用lockHardwareCanvas方法可以返回一个使用GPU进行渲染的Canvas)


SurfaceView

​ SurfaceView = Surface + 特殊的View

​ SurfaceView在View体系中只是一个透明的占位符,与View本身的渲染毫无关系,是独立的。SurfaceView在即将显示的时候会去SurfaceFlinger那请求一个单独的Surface,通过SurfaceHolder异步获取到Surface后,对该Surface进行渲染。SurfaceView的Surface在SurfaceFlinger中默认是放置在应用界面Surface的后面。

​ 采用单独Surface对性能会有很大的提升,因为缓冲区将直接由SurfaceFlinger进行合并然后发送给显示器,不受应用本身绘制的影响,也不会导致应用重绘。但是缺点也很明显,SurfaceView并不是一个真正的View,对它进行大小位置的变动会非常麻烦,因为每次变动都需要通知SurfaceFlinger来改变Surface的大小位置。

​ 在使用的过程中一般都会在异步线程中对SurfaceView进行渲染。


GLSurfaceView

​ GLSurfaceView = SurfaceView + EGL

​ GLSurfaceView会创建一个单独的渲染线程(因为GLES的状态机是和线程绑定的),然后会初始化EGL上下文,封装Render作为GLES的生命回调。


SurfaceTexture

​ SurfaceTexture = Consumer + GLES外部纹理

​ SurfaceTexture拥有一个BufferQueue,可以用于缓冲区的流动。将SurfaceTexture发送给生产方(例如Camera),生产方会将缓冲区写入到队列中,通过SurfaceTexture的onFrameAvailable方法可以获得通知。

​ SurfaceTexture的另一个特点是其在创建BufferQueue的时候,会添加GRALLOC_USAGE_HW_TEXTURE标记,这样gralloc创建的缓冲区就可以被GLES使用。所以SurfaceTexture可以绑定一个OES纹理,通过updateTexImage方法可以从BufferQueue中获取新的缓冲区给OES纹理使用。

​ BufferQueue传递的不仅仅只是缓冲区数据,还有一些附加参数,比如缓冲区的时间戳、转换矩阵。获取附加参数一般在updateTexImage方法后,通过getTransformMatrix方法可以获取前面出队的缓冲区的转换矩阵,之所以传递转换矩阵,而不是将数据在传递前就转换,是因为在真正使用的过程中,数据可能会经过多次流动,会产生多次转换,传递转换矩阵可以使多次转换合并,降低开销;通过getTimestamp获取该缓冲区生产的时间戳,采用生产的时间戳而不是用消费的时间戳是因为有些场景(比如录像)对时间非常敏感。

SurfaceTexture可以认为是GLConsumer,是一个BufferQueue的所有方和消费方;通过SurfaceTexture创建Surface对象,实际上是创建了一个BufferQueue的生产方。


TextureView

TextureView = SurfaceTexture + View

TextureView是一个真正意义上的View,受应用绘制体系控制,不是独立的。TextureView内部会创建一个SurfaceTexture,并将它交给应用的View体系,然后对SurfaceTexture设置onFrameAvailable监听,当BufferQueue中有变动时,先通过updateTexImage方法从SurfaceTexture中获取到最新的缓冲区,然后调用invalidate方法通知应用的View体系将缓冲区合并到应用本身的Surface上。

与SurfaceView相比,TextureView是一个真正的View,对它进行大小位置的变动会很简单高效。但是缺点也很明显,需要先将TextureView上的缓冲区合并到应用的缓冲区(会对TextureView上层的View造成重绘),然后再由SurfaceFlinger对所有Surface进行合并,这样TextureView的内容实际上是被合并了两次,相比SurfaceView,性能消耗比较大。