android图像显示底层远原理

概要

android应用程序显示的过程:android应用程序调用SurfaceFlinger服务把经过测量、布局和绘制后的Surface渲染到显示屏幕上。

SurfaceFlinger:android系统服务,负责管理android系统的帧缓冲区,即显示屏幕。

Surface:android应用的每个窗口对应一个画布(Canvas),即Surface,可以理解为android应用程序的一个窗口。

android应用程序的显示过程包含了两个部分(应用侧绘制、系统侧渲染)、两个机制(进程间通讯机制、显示刷新机制)。

应用侧绘制

一个android应用程序窗口里面包含了很多UI元素,这些元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,如下图:

因此,在绘制一个android应用程序窗口的UI之前,首先要确定里面的各个子UI元素在父UI元素里面的大小和位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程有称为测量过程和布局过程。因此,android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段。

  • 测量:递归(深度优先)确定所有视图的大小(宽、高)
  • 布局:递归(深度优先)确定所有视图的位置(左上角坐标)
  • 绘制:在画布canvas绘制应用程序窗口的所有视图

android目前有两种绘制模型:基于软件的绘制模型和硬件加速的绘制模型。

在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:

  • 让View层次结构失效
  • 绘制View层次结构

当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate方法。无效消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,android系统会在View层次结构中绘制所有的跟脏区相交的区域。这种方法有两个缺点:

  • 绘制了不需要重绘的视图(与脏区相交的区域)
  • 掩盖了一些应用的bug(由于会重绘与脏区相交的区域)

在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:

  • 让View层次结构失效
  • 记录、更新显示列表
  • 绘制显示列表

这种模式下,android系统依然会使用invalidate方法和draw方法来请求屏幕更新和展现View对象。但android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录在一个显示列表中,然后再读取显示列表中的绘制指令然后调用OpenGL相关函数完成实际绘制。另一个优化是,android系统只需要针对由invalidate方法调用锁标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象则能重放先前显示列表记录的绘制指令来进行简单的重绘工作。

使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,以便下次重用,然后再调用OpenGL完成绘制。

硬件加速提高了android系统显示和刷新的速度,但是存在三个缺陷:

  • 兼容性(部分绘制函数不支持或不完全硬件加速)
  • 内存消耗(OpenGL API调用就会占用8MB,而实际上会占用更多内存)
  • 电量消耗(GPU耗电)

系统侧渲染

android应用程序在图形缓冲区中绘制好View层次结构后,这个图形缓冲区会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。

android图像显示的底层原理

CPU:作用是计算图片的形状和文字的纹体

GPU:功能是渲染图像的颜色

Display:屏幕显示图像

Vsync:垂直同步信号,显卡输出频率与屏幕刷新频率同步的信号

android图像在绘制的时候,首先是CPU计算出图像形状,计算完成CPU会将图像交给GPU渲染出颜色,如果这一切都能够在16ms内完成,那么在下一个VSync出现时,就能显示刚刚渲染出来的那一帧图像了。但是如果CPU和GPU处理一帧图像时间超过16ms,那么这帧图像只能等到第二个VSync出现时才能刷出屏幕,呈现给用户了,这就意味着用户在32ms内所看到的是同一帧图像,这就是所谓的掉帧,也就是卡顿了。

CPU、GPU处理图像时间过长的因素:

  • 过度绘制

    UI布局中存在大量重叠的部分,或者重叠背景。

  • 动画使用次数过多

    通常情况下android需要将xml布局文件转换成GPU可以识别的绘制对象,而这些绘制对象被存放在DisplayList数组中,当View第一次绘制的时候DisplayList被创建,View第二次绘制的时候GPU就直接从DisplayList获取绘制对象,省去了测量、布局的时间,但是如果我们改变了View的绘制内容那么得重新测量、布局,DisplayList也会被重新创建,所以动画是非常消耗性能的,因为它必须多次重绘。

  • 短时间内创建的对象过多

    android的垃圾回收机制是,当Heap被占用的空间达到一个阈值,GC就开始回收对象了,GC工作的时候大部分线程是阻塞的,所以如果GC耗时超过16ms那么也会出现失帧卡顿的现象。

进程间通讯机制

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

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

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

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

在每个SharedClient中,最多有31个SharedBufferStack,每个SharedBufferStack都对应一个Surface,即一个窗口。一个SharedClient对应一个android应用程序,而一个android应用程序可能包含多个窗口,但至多可以包含31个窗口。每个SharedBufferStack中又包含了N个缓冲区(android4.1以下,N = 2,4.1以上,N = 3),即显示刷新机制中即将提到的双缓冲和三缓冲技术。

显示刷新机制

一般我们在绘制UI的时候,都会采用一种称为“双缓冲”的技术。双缓冲意味着要使用两个缓冲区,其中一个被称为Front Buffer,另外一个被称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。在android4.1中引入了VSync,这类似于时钟中断,每收到VSync中断,CPU就开始处理各帧数据。

但是如果CPU/GPU的FPS小于Display的FPS,会出现如下情况:

为了解决以上问题,又引出了三重缓冲区,如下所示: