View绘制流程介绍

433 阅读5分钟

目标

介绍View绘制前、绘制中、绘制后的主要工作流程,建立起一个初步认知,方便后续深入了解细节。

前置知识

显示的基本步骤

生产图像数据(App、状态栏等) -> 合成图像数据(系统服务SurfaceFlinger) -> 展示图像数据(屏幕)

不同的图像生产者在接收到Vsync信号后开始绘制图形数据,绘制完后将图像数据提交到图像合成服务,图形合成服务端将多份不同位置、透明度的图像数据合成一帧,最后提交到屏幕里显示。

绘制基本步骤.webp

相关系统服务
ActivityManagerService

这个是整个Android framework框架中最为核心的一个服务,管理整个框架中任务、进程管理, Intent解析等的核心实现。虽然名为Activity的Manager Service,但它管辖的范围,不只是Activity,还有其他三大组件,和它们所在的进程。也就是说用户应用程序的生命管理,都是由他负责的。

WindowManagerService

其主要职责是窗口管理,包含了输入事件的分发和管理、窗口大小和位置的管理。

SurfaceFlinger

FrameBuffer合成的服务,将各个应用程序及应用程序中的逻辑窗口图像数据(surface)合成到一个物理窗口中显示(FrameBuffer)的服务程序

相关机制

Vsync信号

由于图像显示体系需要不同进程、模块协作完成,且各模块产生的数据互相影响,如果各个模块都按照自己的节奏来工作,那么显示的数据将会比较混乱,所以Android系统制定了Vsync机制,由系统发出Vsync信号,各模块在接收到信号后同步工作。

Vsync的源头在HWC模块中,其中包含了硬件Vsync与软件Vsync,软件Vsync是硬件的替代品(硬件不可用时),实现方式就是一个专门的线程死循环,每隔一段时间发出一个信号。与HWC对接的是SurfaceFlinger,SurfaceFlinger中会初始化两个Vsync监听线程,一个提供给SurfaceFlinger自己注册监听,另一个提供给App注册监听。

Buffer机制

图像数据在不同进程间传递,就需要一个跨进程传递数据的机制,系统采用了Binder + Buffer机制。 这里的Buffer是指的GraphicBuffer,GraphicBuffer采用了生产者消费者模式来工作,客户端会持有IGraphicBufferProducer(图像生产者),服务端会持有IGraphicBufferConsumer(图像消费者),消费队列在BufferQueueCore,生产者向BufferQueueCore中写数据后,消费者将能收到通知。

在显示系统中,BufferQueue的消费端位于SurfaceFlinger,生产端位于App、相机、状态栏等能产生图像数据的模块,生产端通过IGraphicBufferProducer进行binder通信,获取对应的Buffer,再通过内存映射使消费端与生产端的Buffer指向同一块内存。

BufferQueue.png

绘制流程

在我们日常编码过程中,只需要创建一个View,然后调用Activity的setContentView方法,View就会执行标准的measure(确定大小),layout(确定位置),draw(制作图像数据)的流程,而我们在View的onDraw回调中拿到Cancas进行绘制即可,使用方式比较简单,这主要是系统封装了一大堆我们看不到的逻辑。

整体流程图

View显示流程.jpg

绘制前

我们的绘制都是在接收到Vsync信号后,通过Canvas进行的 ,而Canvas又将数据保存在了GraphicBuffer里,所以我们这里关注的逻辑有GraphicBuffer的申请,Canvas的创建,Vsync信号注册监听,具体参考流程图。

VSync的注册

当Activity调用了onResume之后,会触发ViewRootImpl的构建,ViewRootImpl先向WMS添加窗口,接着就会调用ViewRootImpl.requestLayout(),内部就会通过Choreographer去向SurfaceFlinger注册Vsync回调,SurfaceFlinger与App通过Socket通信。

Canvas与Buffer的创建

想要申请GraphicBuffer,首先要获取一个IGarphicBufferProducer,再通过Producer.dequeueBuffer()获取一个Buffer。其中Producer是APP不能直接获取的,必须通过WindowManagerService获取,这是系统的限制,因为不能让不受系统控制的应用随意显示。

APP获取IGarphicBufferProducer的方法是调用WindowManagerService.relayoutWindow(),代码位于ViewRootImpl中,执行完后会将producer封装到Surface中,然后调用Surface.lockCancas()时则创建出绘制所需的Canvas,此方法会同时会执行IGarphicBufferProducer.dequeueBuffer申请一个Buffer。

绘制中

在准备工作完成后,会通过Surface.lockCanvas获取一个Cancas对象,通过View.draw传递下去,如果是软件绘制的话,将会获得一个普通的Cancas,如果是硬件绘制将会获取一个RecordingCanvas 。

软件绘制

onDraw中接收到的是普通的Cancas,在调用Canvas.drawXxx的Api时,会直接通过Skia引擎绘制图像数据。

硬件绘制

onDraw中接收到的是RecordingCanvas,在调用drawXxx时是先将这些命令记录起来,不会直接产生图像数据,随后将命令集交给专属的绘制线程,绘制线程会与主线程协作完成UI绘制,具体分工如下。

主线程:收集需要绘制的内容,生成一个个对应的绘制命令与其对应的数据,如DrawColorOp、DrawLineOp等。

绘制线程:在主线程收集完绘制命令后,会同步主线程中收集的绘制内容,然后调用OpenGL(Valuken)绘图。

绘制线程在同步完绘制指令后,主线程就能够被释放去干别的了。

软硬绘制对比.jpg

绘制后

图像数据(Buffer)的提交

buffer提交也会分为软件绘制与与硬件绘制,软件绘制会在onDraw结束后直接调用unlockAndPostCanvas,Surface内部通过IGarphicBufferProducer.queueBuffer()通知SurfaceFlinger有新的图像数据待合成,而硬件绘制则稍微晚点(也是上述API),因为onDraw结束后,绘制工作尚未完成。

合成处理

App提交Buffer后,SurfaceFlinger将会收到通知,首先将Buffer中的内容生成纹理,然后进行合成,合成的简单理解就是按照Z-Order一层一层的的绘制到FrameBuffer上,最后通过EGL交换前后缓冲区即可将图像显示在屏幕上。