安卓渲染原理:Android布局渲染优化底层原理分析
Author:zhoulujun Date:
总的来说,安卓界面的卡顿原因就在于没有必要的布局、invalidations失效以及Overdraw重新绘制屏幕。
通过Hierarchy Viewer去检测渲染效率,去除不必要的嵌套
在android studio中tools下面的android下的android device monitor就能看到。Hierarchy Viewer可以很方便可视化的查看屏幕上套嵌view结构,是查看你的view结构的实用工具。
通过Show GPU Overdraw去检测Overdraw,最终可以通过移除不必要的背景以及使用canvas.clipRect解决大多数问题。
如设置了主布局文件的背景颜色,就可以移除listview以及listview的items中的一些background。以及当我们在layout中设置了背景色,activity的view中的背景就没有必要了,可以调用getWindow().setBackgroundDrawable(null);设置
当我们绘制了多个view的时候,如果每一个view并不需要完全绘制在屏幕上时,我们就可以在onDraw方法中使用canvas.clipRect(0, 0, 0,0);方法
Debug GPU overdraw菜单里选择“Show Overdraw areas”选项。选择之后,会在app的不同区域覆盖不同的颜色来表示overdraw的次数。比较屏幕上这些不同的颜色,可以快速方便的定位overdraw问题。
推荐阅读《关于过度绘制和渲染的介绍》、《Android性能优化-过度渲染》
但是这些问题的原因究竟是为什么?
一.CPU与GPU结构
现在大部分移动端都会配有CPU(中央处理器)和GPU(图形处理器),有的现在还有一块NPU用于处理智能运算。来简单看一下他们的结构;
CPU需要很强的通用性来处理各种不同的数据类型,同时又要进行复杂的数学和逻辑运算,所以使得CPU的内部结构异常复杂;
CPU被Cache占据了大量空间,还有很多复杂的控制逻辑和诸多优化电路,其实计算能力只是CPU很小的一部分,在早期的时候,CPU除了做逻辑计算外,还负责内存管理、图形显示等操作因此在实际运算的时候性能会大打折扣,而且还不能显示复杂的图形,完全不能满足现在3D游戏的要求;所以GPU应运而生。
GPU 出现前 CPU 在图形处理领域的情况 :
承担工作多 : GPU 没有出现之前 , CPU 要承担很多工作 , 如逻辑运算 , 内存管理 , 显示控制 , 界面渲染 等操作 ;
设备弊端 : 不能显示复杂的图形 , 不能运行渲染逼真的游戏 , 如大型 3D 游戏等 ;
CPU 在图形领域的性能瓶颈 : CPU 即使超过 2GHz 的主频 , 其运算能力并不能完全发挥出来 , 无法显示复杂画面 , 不能提高图形绘制的质量 ;
鉴于上述 CPU 的各种弊端 , 就有了 GPU 的设计 , CPU 将显示相关的计算交给 GPU 完成 ;
GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境,所以结构也大不相同。
GPU采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了Cache,GPU将计算机系统所需要的显示信息进行转换驱动,并向显示器提供行扫描信号,控制显示器的正确显示,主要负责图形显示部分的工作。
CPU 与 GPU 架构 :
控制单元 ( 黄色部分 ) : 控制器 , 控制 CPU 运行工作 , 执行如 取出指令操作 , 控制其它模块运行 ;
计算单元 ( 绿色部分 ) : 算术逻辑单元 , 负责数学运算 , 逻辑运算 ;
存储单元 ( 橙色部分 ) : Cache 高速缓存器 , DRAM , 用于存储 CPU 运算信息 ;
CPU 与 GPU 对比 :
逻辑算术运算 : 图像处理时 , 大量使用逻辑运算 , 如 RGB 像素值的位运算 ; GPU 的计算单元多于 CPU , 因此 GPU 的逻辑运算能力强于 CPU ;
程序执行逻辑 : CPU 中控制单元与存储单元功能强大 , 控制程序运行的能力远远高于 GPU ;
总结 : GPU 适合用于大量的复杂的算术逻辑计算 , 如图像运算 , 声音运算等 ; CPU 适合用于控制系统 , 应用运行 ;
Android 布局显示到屏幕流程
定义布局中的组件 : 在 xml 布局文件中定义 ImageView 布局 ;
加载组件到内存 : 通过 LayoutInflater 将该 ImageView 组件解析成 ImageView 对象 , 加载到内存中 , 该对象中封装了组件位置 , 显示图片等信息 ;
CPU 处理 : 将上述 ImageView 对象进行计算处理 , 最终得到该组件对应的多维向量图形 ( 使用向量表示的图形 ) ;
GPU 处理 : GPU 接收上述多维向量图形 , GPU 将该向量图进行栅格化 , 将向量图转为位图 ( 矢量图转为像素图 ) , 计算出对应屏幕上每个像素点显示的值 ;
显示器显示 : GPU 向显示器推送位图 , 会判定前面的 444 个步骤花费时间是否小于 16ms , 如果小于该值 , 那么就显示该位图 , 如果大于该值 , 那么不绘制 , 等待下一帧位图绘制完成 , 这是为了避免显示卡顿而设计的机制 , 虽然丢了一帧数据 , 但是显示很流畅 ;
Android系统绘图机制
现在的安卓终端通常在一个典型显示系统中首先由CPU发出图像绘制指令要让GPU去画一个样式,但CPU不能直接和GPU通信,也要遵守相应的规则,就和现在我们干什么事都要走个流程一样的嘛,不能乱套;所以CPU要先向OpenGL ES发送一些指令,表达要画一个样式,Opengl ES是一组接口API,**通过这些API可以操作驱动,让GPU达到各种各样的操作;GPU接收到这些命令,开始栅格化处理,把样式显示到屏幕中;
现在我们把应用加到显示流程里面来
简单介绍一下矢量图和位图
矢量图:由一个函数来描述,这个函数描述了此图如何生成
位图:由像素点矩阵来描述
Android系统每隔16ms就重新绘制一次Activity,所以要求应用必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧(60FPS),然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是60赫兹(是每秒中的周期性变动重复次数的计量),如果超过了16ms就会出现所谓的丢帧(1000ms/60=16.66ms)
一帧图像完整渲染过程
在Android应用程序窗口里面包含了很多视图(View)元素,这些元素是以树形结构来组织,最终构成所谓视图树的结构;
在绘制一个Android应用程序窗口的UI之前,要确定它里面的各个子View元素在父元素里面的大小以及位置。确定各个子View元素在父View元素里面的大小以及位置的过程又称为测量过程和布局过程。
Android应用程序窗口的UI渲染过程可以分为三个阶段:(由ViewRootImpl类的performTraversals()方法发起)
Measure(测量)——递归(深度优先)确定所有视图的大小(高、宽)
Layout(布局)——递归(深度优先)确定所有视图的位置
Draw(绘制)——在画布canvas上绘制应用程序窗口所有的视图
经过多次绘制后,这一帧内要显示的所有view都已经被绘制完毕,注意绘制View层次结构这些操作是在图形缓冲区中绘制完成的;
此时就要把这个图形缓冲区被交给SurfaceFlinger服务
SurfaceFlinger服务概述:
SurfaceFlinger服务和其他系统服务一样是在Android系统的System进程里被启动并运行在其中的,主要负责统一管理设备中Android系统的帧缓冲区(Frame Buffer,简单理解为屏幕所显示出来的所有图形效果都是由它统一管理的),在SurfaceFlinger服务启动的过程中会自动创建两个线程:
一个线程用于监控控制台事件
一个线程则用于渲染系统的UI;
Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,就需要将UI数据传递SurfaceFlinger服务并告知自己具体的UI数据(例如要绘制UI的区域、位置等信息),
Android应用程序与SurfaceFlinger服务是运行在不同的进程中,所以相互间通过Binder机制进行通信,
大致可以分为3步:
首先是创建一个到SurfaceFlinger服务的连接,
通过这个连接来创建一个Surface,
请求SurfaceFlinger服务渲染该Surface(在Android应用的每个窗口对应一个画布(Canvas),也可以理解为Android应用程序的一个窗口)
在APP层我们对于这部分的无法进行任何的优化,这是ROOM做的工作。
简单来说就是当Android应用层在图形缓冲区中绘制好View层次结构后,应用层通过Binder机制与SurfaceFlinger通信并借助一块匿名共享内存会把这个图形缓冲区会被交给SurfaceFlinger服务。因为单纯的匿名共享内存在传递多个窗口数据时缺乏有效的管理,所以匿名共享内存就被抽象为一个更上流的数据结构SharedClient,在每个SharedClient中,最多有31个SharedBufferStack,每个SharedBufferStack都对应一个Surface即一个窗口。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
VSync机制
为了减少卡顿,Android 4.1(JB)中已经开始引入VSync(垂直同步)机制
简单来说就是CPU/GPU会接收vsync信号,Android系统每隔16ms发出Vsync信号,触发对UI 进行渲染(即每16ms显示一帧)
在16ms内需要完成两项任务:将UI 对象转换为一系列多边形和纹理(栅格化)和CPU传递处理数据到GPU。
但即使引入垂直同步机制也不是非常完美,如果某些原因导致CPU和GPU渲染某一帧画面的时间超过16ms时,Vsync垂直同步机制会让硬件显示器等待,直到GPU完成栅格化操作,这就直接导致这一帧画面多停留了16ms甚至更长时间,让用户看起来画面停顿。
渲染超时卡顿分析
人眼的视觉相关分析
Android 刷新帧率 :
最低流畅帧率 : 保持画面流畅的最低帧率是 60FPS , 当帧率低于 60 FPS 时 , 就会画面卡顿的感觉 ;
60 帧率对应的每一帧刷新间隔 : 660/1000=16.66 , 即每隔 16.66 毫秒刷新一次 ;
Android 设备刷新机制 : Android 中每隔 16ms 就会发出 VSYNC 信号通知屏幕该进行渲染 , 每次渲染的时间都必须小于 16 毫秒 , 才能保证 60 FPS 的帧率 ; 如果渲染时间大于 16 毫秒 , 就无法保证 60 FPS 的帧率, 此时就会造成卡顿 ;
人眼对于各个帧率的接受程度 :
12 FPS : 达到这个帧率 , 人眼可以认为该图像是连续的动作 , 如 GIF 图像 , 翻动作小人书等 ;
24 FPS : 初期的电影动画的帧率 , 勉强接收 ;
30 FPS : 早期的电子游戏 , 要求高于电影 ;
上面的三种都是人与视频内容不交互 , 或少量交互 , 人感觉不出来卡顿 ;
60 FPS : 在交互频繁的游戏中 , 低于 60 FPS , 是可以感觉出来的 , 因此动作类的游戏尽量都要达到 60 FPS ;
60 FPS 以上 : 60 FPS 与 144 FPS 是等效的 , 人眼察觉不到这个差异 ;
打游戏时 , 感觉很卡 , 说明帧率低于 60 帧了 , 越低迟滞感越强烈 ;
渲染超时卡顿分析
VSync 信号 : Android 每隔 16 毫秒发出 VSync 信号 , 屏幕接收到该信号时 , 开始显示渲染好的位图 , CPU 和 GPU 开始渲染新的图像 ;
渲染与显示时间固定 : 渲染开始 与 屏幕绘制的时间都是固定的 , 就是 VSync 信号发出时间 , 并且其间隔必须是 16 毫秒 , 在固定的时间开始渲染 , 在固定的 16 毫秒之后 , 显示到屏幕中 , 这样就是固定的 60Hz 的屏幕刷新频率 ;
渲染提前完成 : 渲染可以提早完成 , 如 CPU 和 GPU 在 10 毫秒时已经渲染完毕 , 将向量图栅格化后的位图传递给屏幕 , 此时等待 6 毫秒后 , 屏幕触发显示操作 , 将已经渲染完毕的位图显示出来 ;
显然超时未完成 : 在某个固定的时间 , 开始渲染图片 , CPU , GPU 对布局组件对应画面进行渲染后 , 如果从开始渲染 , 到显示器显示之间的时间间隔超过了 16 毫秒 , 屏幕在 16 毫秒的时刻接收 VSync 信号触发显示 , 但是此时还处于渲染阶段 , 没有将位图传递给屏幕 , 因此仍然显示上一帧图片 , 这里就少了一帧 , 变成了 59 Hz 的刷新频率 , 如果这种超时很多 , 变成 40Hz , 30Hz , 那就非常卡了 ;
文章摘要内容地址:
扒一扒安卓渲染原理 https://gameinstitute.qq.com/community/detail/133403
【Android 性能优化】布局渲染优化 ( CPU 与 GPU 架构分析 | 安卓布局显示流程 | 视觉与帧率分析 | 渲染超时卡顿分析 | 渲染过程与优化 ) https://blog.csdn.net/shulianghan/article/details/106908414
转载本站文章《安卓渲染原理:Android布局渲染优化底层原理分析》,
请注明出处:https://www.zhoulujun.cn/html/OS/Android/Fundamentals/8508.html