文章目录
在从Android 6.0源码的角度剖析View的绘制原理一文中,我们了解到View的绘制流程有三个步骤,即measure(
测量
)、layout(布局
)和draw(绘制
),它们主要运行在系统应用框架层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFlinger服务
来完成的。本文将暂不对SurfaceFlinger服务
的执行流程进行剖析,而是从更加底层的角度来了解APP的渲染机制,同时引出在渲染过程中遇到的性能问题,以及如何去发现、优化它们。
1. 渲染机制分析
1.1 渲染机制
对于开发者来说,APP的界面主要是由XML布局文件
来表现的,每个XML布局文件
中又包括了很多视图组件,比如ImageView、Button、TextView等等,它们分别表示了图片、按钮、字符串等不同的信息,那么这些复杂的XML布局文件和标记语言,又是如何转化成为用户能够看得懂的图像的呢?答案是:格栅化(Rasterization)
!所谓格栅化,就是将例如字符串
、按钮
、路径
或者形状
等的一些高级对象,拆分到不同的像素屏幕上进行显示。格栅化是一个非常费时的操作,CPU作为中央处理器本身任务就比较繁重,因此人们引入了GPU(图像处理器)
这块特殊的硬件来加快格栅化操作(硬件加速)。格栅化操作大体如下:
接下来,我们就来详细了解XML布局文件中的UI组件是如何被格栅化并渲染显示在屏幕上的?在APP绘制渲染过程中,与绘制渲染有关的硬件主要有CPU和GPU(图形处理器),其中,CPU负责把UI组件计算成Polygons(多边形)
或Texture(纹理)
,而GPU负责格栅化和渲染工作。CPU和GPU通过图形驱动层
进行连接,这个图形驱动层
维护了一个Display List
队列,它们分别充当生产者
和消费者
,协作完成具体的绘制渲染过程。绘制渲染过程如下图所示:
- 渲染流程如下:
首先
,CPU
会对UI组件(View)进行Measure
、Layout
、Record
、Execute
的数据计算工作,以将其计算成的Polygons(多边形)
或Texture(纹理)
,它们是GPU能够识别的对象,而承载这些信息的是一个被称为Display List
的结构体,它持有所有交给GPU绘制到屏幕上的数据信息,包含GPU要绘制的全部对象的信息列表和执行绘制操作的OpenGL命令列表。在某个View第一次需要被渲染的时候,Display List
会因此被创建,当这个View需要显示到屏幕上时,GPU接收到绘制指令后会执行该Display List
。每个View拥有自己的Display List
,Display Lis
本身也构成一个树状的结构,跟View Hierachy(视图树)
保持一致。
其次
,由于每次从CPU计算得到的Polygons
或Texture
提交(转移)GPU是一件比较麻烦且耗时的事情(注:实际提交的是Display List
),因此Android系统又引入了OpenGL ES,该库API允许将那些需要渲染的Polygons
或Texture
存储(Hold)在GPU存储器(显存)中,当下一次需要渲染的时候只需要在GPU存储器里引用它,然后告诉OpenGL如何绘制就可以了,而不再需要经过CPU上传。需要注意的是,假如View的绘制内容发生变化,那么GPU Memory存储的Display List
就无法再继续使用,这时就需要CPU重新计算创建Display List
并重新执行指令更新到屏幕。
最后,当GPU完成对纹理的格栅化、渲染后,它会将渲染的结果放入帧缓冲区
,视频控制器会按照VSync(垂直同步)
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
- CPU、OpenGL与GPU之间的关系
1.2 卡顿现象
前面说到,当GPU渲染完成后会将渲染的结果存储到帧缓冲区
,视频控制器会按照VSync(垂直同步)
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。那么问题来了,什么是VSYNC信号?在讲解VSYNC之前,我们需要了解两个相关的概念:Refresh Rate
和Frame Rate
。
Refresh Rate(刷新频率)
表示屏幕在一秒内被刷新的次数,取决于硬件的固定参数,目前大部分手机这个固定参数(屏幕刷新频率)为60Hz
。也就是说,Android系统每隔约16ms(1000ms/60=16.66ms)
就会重新刷新一次界面(Activity),因此我们有16ms的时间去完成每帧的绘制、渲染的逻辑操作。
Frame Rate(帧率)
表示GPU在一秒内能够渲染的帧数,通常为60fps
。为什么是60fps
?这是因为人眼与大脑之间的协作无法感知超过60fps
的画面更新。简单的说,人眼看到的动画其实都是一帧帧连续播放的静态图片,当播放的速度达到1秒针24帧(24fps
)时,对于人眼感知来说就是连续的线性运动。当然低于30fps在某些场景仍然无法顺畅表现绚丽的画面,此时就需要60fps
来达到想要的效果。
现在我们再来理解下VSYNC信号:**屏幕的刷新过程是每一行从左到右(水平刷新,Horizontal Scanning
),从上到下(垂直刷新,Verical Scanning)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,会有短暂的空白期,此时Android系统就会发出VSYNC信号,触发下一帧对UI的渲染、显示。VSYNC是一种定时中断,一旦收到VSYNC信号CPU就开始处理帧数据,通常Android系统每隔16ms发出VSYNC信号,这个周期时间由屏幕刷新频率决定。**通常来说,帧率超过刷新频率只是一种理想的状态,在超过60fps
的情况下,GPU所产生的帧数据会因为等待VSYNC
的刷新信息而被Hold住,这样能够保持每次屏幕刷新都有实际的新的数据可以显示。但是基本上我们遇到的是帧率<=屏幕刷新频率
的情况,尤其是在帧率小于刷新频率时,会出现待VSYNC
信号到来时,屏幕没有可以刷新的数据,即帧缓冲区还是之前的那帧图像,这就会导致屏幕显示的该帧画面内容仍然是上一帧的画面,而这种现象我们就称之为"掉帧
",对于用户来说,就是界面出现了卡顿现象
,即运行不流畅。
由此我们可以得出,