![](https://img.haomeiwen.com/i2573196/89913ba9ee222614.png)
UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识、如何优化 UI 渲染两部分内容。
UI 优化系列专题
- UI 渲染背景知识
《View 绘制流程之 setContentView() 到底做了什么?》
《View 绘制流程之 DecorView 添加至窗口的过程》
《深入 Activity 三部曲(3)View 绘制流程》
《Android 之 LayoutInflater 全面解析》
《关于渲染,你需要了解什么?》
《Android 之 Choreographer 详细分析》
- 如何优化 UI 渲染
《Android 之如何优化 UI 渲染(上)》
《Android 之如何优化 UI 渲染(下)》
UI 渲染可能是诸多性能问题中最容易被察觉到的,Android 开发既要面对各式各样的手机屏幕尺寸和分辨率,还要与“凶残”的产品和 UI 设计师过招。在正确实现复杂、炫酷的 UI 设计的同时,还需要保证流程的用户体验。更加不幸的是,最近几年这个趋势似乎愈演愈烈:刘海屏、水滴屏、全面屏,还有即将推出的的柔性折叠屏,UI 适配将变得越来越复杂。
UI 渲染的背景知识
Android 的图形渲染框架十分复杂,不同版本的差异也比较大。但是无论怎么样,它们最终都是为了将我们代码中的 View 或者元素显示到屏幕上。
而屏幕作为直接面对用户的手机硬件,类似厚度、色彩、功耗等都是厂家非常关注的。从早期的黑白屏功能机,到现在的超大的全面屏,我们先来看看手机屏幕的发展历程。
1. 屏幕与适配
Android 的碎片化问题由来已久,并且另每个 Android 开发痛心疾首,而屏幕的差异化正是这些碎片化问题的“中心”。屏幕从 3 英寸到 10 英寸,分辨率从 320 到 2160 应有尽有,对我们 UI 适配造成很大困难。
除此之外,材质也是屏幕至关重要的一个评判因素。目前智能手机主流的屏幕可分为两大类:一种是 LCD(Liquid Crystal Display),即液晶显示器;另一种是 OLED(Organic Light-Emitting Diode )即有机发光二极管。
最新的旗舰机例如 iPhone 11 Pro / Max 已经开始使用超视网膜 OLED 显示屏技术,OLED 提供令人惊叹的高对比度和高分辨率,相比 LCD 屏幕,OLED 无需背光组件,而是由每个像素自行发光,因此显示屏变得更薄。另外 OLED 屏幕在可弯曲程度以及耗电方面都更具有优势。正因为这些优势,全面屏、曲面屏以及未来的柔性折叠屏,使用的都是 OLED 材质。关于 OLED 与 LCD 的具体差别,可以参考《OLED 和 LCD 区别》和《手机屏幕的前世今生,可能比你想象的还精彩》,不过目前 OLED 的成本相比 LCD 要高很多。
对于屏幕适配,Android 推荐使用 dp 作为尺寸单位来适配 UI,通过 dp 加上自适应布局基本可以解决屏幕碎片化问题,这也是 Android 推荐使用的屏幕兼容性适配方案。因此每个 Android 开发都应该清楚 px、dp、dpi、ppi、density 这些概念。具体可以参考《Android 屏幕适配全攻略》
2. CPU 与 GPU
除了屏幕,UI 渲染还要依赖另外两个核心的硬件:CPU 和 GPU。
- CPU(Central Processing Unit,中央处理器),是计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元;
- GPU(Graphics Processin Unit,图形处理器),是一种专门用于图像运算的处理器,在计算机系统中通常被称为 "显卡"的核心部件就是 GPU。
UI 组件在绘制到屏幕之前,都需要经过 Rasterization(栅格化)操作,而栅格化又是一个非常耗时的操作。
![](https://img.haomeiwen.com/i2573196/1f91733ddd86b5b5.png)
- Rasterization 栅格化是绘制那些 Button、Shape、Path、String、Bitmap 等显示组件最基础的操作。栅格化将这些 UI 组件拆分到显示器的不同像素上进行显示。这是一个非常耗时的操作,GPU 的引入就是为了加快栅格化。
GPU 的由来
在没有 GPU 的时代,UI 的绘制任务完全由 CPU 完成, CPU 既要负责 UI 绘制还要负责内存管理、逻辑运算等其他任务,这就导致 CPU 的任务繁多,因此性能也会大打折扣。
CPU 与 GPU 在结构设计上完全不同,如下图:
![](https://img.haomeiwen.com/i2573196/832f3b7be3ec3b2d.png)
-
黄色 Control 为控制器,用于协调控制整个 CPU 的运行,包括指令读取、控制其他模块的运行等;
-
绿色的 ALU(Arithmetic Logic Unit)是算数逻辑单元,用于进行数学、逻辑运算;
-
橙色的 Cache 和 DRAM 分别为高速缓存和 RAM,用于存储信息。
从结构图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少,因此 CPU 更擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。
而 GPU 的设计正是为实现大量数学运算。GPU 的控制器比较简单,但包含大量 ALU。GPU 中的 ALU 使用了并行设计,且具有较多的浮点运算单元。可以帮助我们加快栅格化操作。
![](https://img.haomeiwen.com/i2573196/f91e3bb3c49cadda.png)
从图中可以看到,软件绘制使用 Skia 库,它是一款能在低端设备,如手机呈现高质量的 2D 跨平台图形框架,类似 Chrome、Flutter 内部使用的都是 Skia 库。
- 硬件绘制的思想就是通过底层软件代码,将 CPU 不擅长的图形计算转换成 GPU 专用指令,由 GPU 完成绘制任务。
3. OpenGL 与 Vulkan
- OpenGL(Open Graphics Library):OpenGL 是一个跨平台的图形API,它为 3D 图形处理硬件指定了一个标准的软件接口。
- OpenGL ES (Embedded Systems):OpenGL ES 是针对嵌入式设备的 OpenGL 规范的一种变体。Android 支持多个版本的 OpenGL ES API。
- Vulkan:它是一个低开销、跨平台的 3D 图形和计算 API。Vulkan 的目标是跨所有平台的高性能实时 3D 图形应用程序,旨在提供更高的性能和更均衡的 CPU / GPU 使用。
硬件加速绘制就是通过 GPU 来进行渲染,GPU 作为一个硬件,用户空间是无法直接使用的,它是由 GPU 厂商按照 OpenGL 规范实现的驱动间接进行使用。也就是说,如果一个设备支持 GPU 硬件加速渲染,那么当 Android 应用程序调用 Open GL 接口来绘制 UI 时,Android 应用程序的 UI 就是通过硬件加速技术进行渲染的。
在官方硬件加速的文档中,可以看到很多 OpenGL API 都有相应的 Android API Level 限制。
![](https://img.haomeiwen.com/i2573196/dad2fa44244eeef7.png)
这主要是受 OpenGL ES 版本与系统支持的限制,直到 Android P,仍然有 3 个 API 是没有支持的。对于不支持的 API,只能使用软件绘制模式,渲染的性能将会大大降低。
OpenGL ES版本 | Android 版本 |
---|---|
OpenGL ES 1.0 和 1.1 | Android 1.0 支持 |
OpenGL ES 2.0 | Android 2.2 支持 |
OpenGL ES 3.0 | Android 4.3 支持 |
OpenGL ES 3.1 | Android 5.0 支持 |
OpenGL ES 3.2 | Android 7.0 支持 |
Android 7.0 把 OpenGL ES 升级到最新的 3.2 版本的实时,还添加了对 Vulkan 的支持。Vulkan 的设计目标是取代 OpenGL,Vulkan 是一个相当低级别的 API,并且提供并行的任务处理。Vulkan 还能够渲染 2D 图形应用程序。除了其较低的 CPU 使用率,Vulkan 还能够更好地在多个 CPU 内核之间分配工作。在功耗、多核优化提升绘图调用上有着非常明显的优势。
Android 的图形组件
Android 的 UI 渲染性能是 Google 长期以来非常重视的,基本每次 Google I/O 都会花很多篇幅讲这一块内容。每个开发者都希望自己的应用都可以做到 60 fps 如丝般顺滑,在了解 Android 的渲染之前,需要先了解下 Android 图形系统的整体架构,以及它包含的主要组件。
![](https://img.haomeiwen.com/i2573196/45271d6dbef1447a.png)
-
Image Stream Produces:图像流生产方,应用程序内绘制到 Surface 的图形(xml / Java 实现的图形,或者视频)
-
Hardware Composer:硬件合成器,它是显示控制器的硬件抽象。
-
Gralloc:Graphic memory allocator 用来分配图形缓冲区内存。
如果把应用程序图形渲染过程当做一次绘画过程,那么绘画过程中 Android 的各个图形组件的作用是:
-
画笔:Skia 或者 OpenGL。我们可以用 Skia 画笔绘制 2D 图形,也可以用 OpenGL 来绘制 2D / 3D 图形。正如前面所说,前者使用 CPU 绘制,后者使用 GPU 绘制。
-
画纸:Surface。所有的元素都在 Surface 这张画纸上进行绘制和渲染。在 Android 中,Window 是 View 的容器,每个窗口都会关联一个 Surface。而 WindowManager 则负责管理这些窗口,并且把它们的数据传递给 SurfaceFlinger。
-
画板:Graphic Buffer。Graphic Buffer 缓冲用于应用于应用程序图形的绘制,在 Android 4.1 之前使用的是双缓冲机制;在 Android 4.1 之后,使用的是三缓冲机制。
-
显示:SurfaceFlinger。它将 WindowManger 提供的所有 Surface,通过硬件合成器 Hardware Composer 合成并输出到显示屏。
使用画笔 Skia / OpenGL 将内容绘制到 Surface 上,绘制的过程中如果使用 Open GL 渲染,那便是硬件加速,否则纯靠 CPU 绘制渲染栅格化的过程就叫软件绘制。对于硬件绘制,我们通过调用 OpenGL ES 接口利用 GPU 完成绘制。
Why 60 fps?
关于 Android 的渲染,大家肯定听说过每秒 60 帧和 16ms 的限制问题,你是否有想过为什么是这些数字?如果你是对于性能要求较高的开发者,这样的技术细节是非常值得深入了解的。
由于人类眼睛特殊的生理结构,并不像相机那样有图像快照送到大脑;相反,大脑在不停地处理眼睛传递给他的视觉信号,所以对于人类的大脑来说并没有帧或者快照的概念,人类眼睛对于运动的概念来自于静止的帧。
-
12 fps:每秒达到 10 ~ 12 帧以上才可以被感知到运动及变化,但是这样的速率是非常不流畅的,只有帧率超过每秒 24 帧的时候,才会被察觉为流畅的运动及变化。
-
24fps:每秒 24 帧在电影界是黄金标准,24 帧的速度足够使画面运动的非常流畅,而且 24 帧的电影预算也能满足成本的要求,这也是为什么在过去的 50 年里,绝大多数的电影都使用 24 帧每秒的速率。
现在,每秒 30 帧对于电影来说绰绰有余,但是对于那些复杂绚丽的电影特效,它的视觉效果还是难以令人信服。
- 60fps:实际上每秒 60fps 的速度才是真正的黄金标准,60 帧的速度非常流畅,绝大多数人都察觉不到比 60 帧还高的视觉体验。
![](https://img.haomeiwen.com/i2573196/c6c72650f4e0ba0d.png)
Android 系统每间隔 16ms 发出一次 VSYNC 信号,触发对 UI 的渲染任务;为了能够实现流畅的画面,这就意味着应该始终让应用保持在 60 帧每秒,即每帧工作的准备时间仅有 16ms。
Android 渲染的演进
跟耗电一样,Android 的 UI 渲染性能也是 Google 长期以来非常重视的,基本每次 Google I/O 都会花很多篇幅讲这一块。每个开发者都希望自己的应用可以做到 60 fps 如丝般顺滑,不过相比 iOS 系统,Android 的渲染性能一直被人诟病。
Android 系统为了弥补跟 iOS 的差距,在每个版本都做了大量的优化。接下来我们通过演进的方式看看 Android 系统在渲染方面都做了哪些努力。
1. Android 4.0 开启硬件加速
在 Android 3.0 之前,或者没有启用硬件加速时,系统都会使用软件方式来渲染 UI。
![](https://img.haomeiwen.com/i2573196/0f9439ced9f93cab.png)
整个流程如上图所示:
-
Surface。每个 View 都由某一个窗口管理,而每一个窗口都会关联有一个 Surface。
-
Canvas。通过 Surface 的 lock 函数获得一个 Canvas,Canvas 可以简单理解为 Skia 底层接口的封装。
-
Graphic Buffer。SurfaceFlinger 会帮助我们托管一个 BufferQueue,我们从 BufferQueue 中拿到 Graphic Buffer,然后通过 Canvas 以及 Skia 将绘制内容栅格化到上面。
-
SurfaceFlinger。通过 Swap Buffer 把 Front Graphic Buffer 的内容交给 SurfaceFlinger,最后硬件合成器 Hardware Composer 合成并输出到显示屏。
整个渲染流程看上去比较简单,但是正如前面所说,CPU 对于图形处理并不是那么高效,这个过程完全没有利用 GPU 的高性能。
硬件加速绘制
所以从 Android 3.0 开始,Android 开始支持硬件加速,但是到 Android 4.0 时才默认开启硬件加速。
![](https://img.haomeiwen.com/i2573196/85d6e5d6fd345aa9.png)
硬件加速绘制与软件绘制整个流程差异非常大,最核心就是通过 GPU 完成 Graphic Buffer 的内容绘制。此外硬件绘制还引入了一个 DisplayList 的概念,每个 View 内部都有一个 DisplayList,当某个 View 需要重绘时,将它标记为 Dirty。
当需要重绘时,仅仅只需要重绘一个 View 的 DisplayList,而不是像软件绘制那样需要向上递归。这样可以大大减少绘图的操作数量,因而提高了渲染效率。
![](https://img.haomeiwen.com/i2573196/7c008de01c32ec18.png)
2. Android 4.1 Project Butter
优化是无止境的,Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,并且在 Android 4.1 中正式开启了这个机制。
Project Butter 主要包含三个组成部分,VSYNC、Triple Buffer 和 Choreographer。
VSYNC 信号
其中 VSYNC(Vertical Synchronization)是理解 Project Butter 的核心。对于 Android 4.0,CPU 可能会因为在忙其他的事情,导致没来得及处理 UI 绘制。
为了解决这个问题,Project Butter 引入了 VSYNC,它类似于时钟中断,每收到 VSYNC 中断,CPU 会立即准备 Buffer 数据,由于大部分显示设备刷新频率都是 60 Hz(一秒刷新 60 次),也就是说一帧数据的准备都要在 16ms 内完成。
![](https://img.haomeiwen.com/i2573196/bdf7a406c55ed74d.png)
这样应用总是在 VSYNC 边界上开始绘制,而 SurfaceFlinger 总是在 VSYNC 边界上进行合成。这样可以消除卡顿,并提升图形的视觉表现。
Triple Buffering(三缓冲机制)
在 Android 4.1 之前,Android 使用双缓冲机制。怎么理解呢?一般来说,不同的 View 或者 Activity 都会共用同一个 Window,也就是共用同一个 Surface。
而每个 Surface 都会有一个 BufferQueue 缓存队列,但是这个队列会由 SurfaceFlinger 管理,通过匿名共享内存机制与 App 应用层交互。
![](https://img.haomeiwen.com/i2573196/2f3faf12f7fbf6f5.png)
整个流程如下:
-
每个 Surface 对应的 BufferQueue 内部都有两个 Graphic Buffer,一个用于绘制一个用于系那是。应用会把内容先绘制到离屏缓冲区(OffScreen Buffer),在需要显示时,才把离屏缓冲区的内容通过 Swap Buffer 复制到 Front Graphic Buffer 中。
-
这样 SurfaceFlinge 就拿到了某个 Surface 最终要显示的内容,但是同一时间我们可能会有多个 Surface。这里面可能是不同应用的 Surface,也可能是同一个应用里面类似 SurfaceView 和 TextureView,它们都会有自己单独的 Surface。
-
这个时候 SurfaceFlinger 把所有 Surface 要显示的内容统一交给 Hardware Composer,它会根据位置、Z - Order 顺序等信息合成为最终屏幕需要显示的内容,而这个内容交给系统的帧缓冲区 Frame Buffer 来显示(Frame Buffer 是非常底层的,可以理解为屏幕显示的抽象)。
如果你理解了双缓冲机制的原理,那就非常容易理解什么是三缓冲区了。如果只有两个 Graphic Buffer 缓存区 A 和 B,如果 CPU / GPU 绘制过程较长,超过了一个 VSYNC 信号周期,因为缓冲区 B 中的数据还没有准备完成,所以只能继续展示 A 缓冲区的内容,这样缓冲区 A 和 B 都分别被显示设备和 GPU 占用,CPU 无法准备下一帧的数据。
![](https://img.haomeiwen.com/i2573196/d8d23267c07bb5d7.png)
如果再提供一个缓冲区,CPU、GPU 和显示设备都能使用各自的缓冲区工作,互不影响。简单来说,三缓冲机制就是在双缓冲机制的基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,带来的坏处是多使用了一个 Graphic Buffer 所占用的内存。
![](https://img.haomeiwen.com/i2573196/bc03874349607be4.png)
Choreographer
Choreographer 本质是一个 Java 类,如果直译的话为舞蹈指导,看到这个词不得不赞叹设计者除了 Coding 之外的广泛视野。舞蹈是有节奏的,节奏使舞蹈的每个动作更加协调和连贯;视图刷新也是如此,Choreographer 可以接收系统的 VSYNC 信号,业界一般通过它来监控应用的帧率。
Choreographer 是线程单例,而且它具有处理当前线程 Looper 的能力。
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
//抛出异常。
}
return new Choreographer(looper);
}
};
3. Android 5.0:RenderThread
经过 Android 4.1 的 Project Butter 黄油计划之后,Android 的渲染性能有了很大的改善。不过你有没有注意到这样一个问题,虽然利用了 GPU 的图形高性能运算,但是从计算 DisplayList,到通过 GPU 绘制到 Frame Buffer,整个计算和绘制都在 UI 主线程中完成。
![](https://img.haomeiwen.com/i2573196/d8ed000341448395.png)
UI 线程“既当爹又当妈”,任务过于繁重。如果整个渲染过程比较耗时,可能造成无法响应用户的操作,进而出现卡顿的情况。GPU 对图形的绘制渲染能力更胜一筹,如果使用 GPU 并在不同线程绘制渲染图形,那么整个流程会更加顺畅。
正因如此,在 Android 5.0 引入两个比较大的改变。一个是引入了 RenderNode 的概念,它对 DisplayList 及一些 View 显示属性都做了进一步封装。另一个是引入了 RenderThread,所有的 GL 命令执行都放到这个线程上,渲染线程在 RenderNode 中存有渲染帧的所有信息,可以做一些属性动画,这样即便主线程有耗时操作的时候也可以保证动画流程。
总结
今天,我们主要介绍了 UI 渲染的理论知识,通过演进的方式进一步加深对 Android 渲染机制的理解,这对接下来要讨论的 UI 渲染优化会有很大的帮助。
Android 在 UI 渲染方面相比 iOS 总是被人诟病,人们总说 iOS 系统更加流程,大家对 Android 的 UI 渲染还了解哪些内容呢?欢迎大家留言或指正。
Android 渲染架构非常庞大和复杂,而且演进得非常快,感兴趣的朋友可以进一步了解学习。
- 2018 Google I/O:Drawn out:How Android renders
- 官方文档:Android 图形架构
- 浏览器渲染:一颗像素的诞生
以上便是个人在学习 Android 渲染时的体会和总结,关于 UI 渲染大家还了解哪些内容?欢迎您的分享!
文章如果对你有帮助,就请留个赞吧!
扩展阅读
- Android 之 Choreographer 详细分析
- Android 之 Project Butter 详细介绍
- 深入 Activity 三部曲(1)View 绘制流程之 setContentView() 到底做了什么
- 深入 Activity 三部曲(2)View 绘制流程之 DecorView 添加至窗口的过程
- 深入 Activity 三部曲(3)View 绘制流程
其他系列专题
网友评论