Android渲染系列之原理概述篇

本篇文章主要宏观整体的介绍Android是如何显示到屏幕上

屏幕硬件

渲染离不开屏幕,Android中的屏幕碎片化比较严重,尺寸大小不一,材质也是屏幕重要的因素。 目前智能手机主流的屏幕可分为两大类即液晶显示器;

  • LCD (Liquid Crystal Display) 液晶显示器
  • OLED (Organic Light Emitting Diode)即有机发光二极管

CPU与GPU

除了屏幕 UI渲染还要依赖另外两个核心的硬件: 「CPU」「GPU」

  • CPU (Central Processing Unit,中央处理器),是计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元
  • GPU (Graphics Processin Unit,图形处理器),是一种专门用于图像运算的处理器,在计算机系统中通常被称为"「显卡」"的核心部件就是 GPU。

UI 组件在绘制到屏幕之前,都需要经过 Rasterization (栅格化)操作,而栅格化又是一个非常耗时的操作。GPU的引入就是为了加快栅格化

GPU简述

在没有 GPU 的时代,UI 的绘制任务完全由 CPU 完成,CPU 既要负责 UI 绘制还要负责内存管理、逻辑运算等其他任务,这就导致 CPU 的任务繁多,因此性能也会有影响. CPU与GPU 在结构设计上完全不同,如下图:

  • 黄色 Control 为控制器,用于协调控制整个 CPU 的运行,包括指令读取、控制其他模块的运行等;
  • 绿色的 ALU (Arithmetic Logic Unit) 是算数逻辑单元,用于进行数学、逻辑运算;
  • 橙色的 Cache 和 DRAM 分别为高速缓存和 RAM,用于存储信息

从上图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少,因此 CPU 更擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算 而 GPU 的设计正是为实现大量数学运算。GPU 的控制器比较简单,但包含大量 ALU。GPU 中的 ALU 使用了并行设计,且具有较多的浮点运算单元。「可以帮助我们加快栅格化操作」

在处理3D场景时,通常Android使用OpenGL ES,不过在Android 7.0 添加了对 Vulkan 的支持

Vulkan 的设计目标是取代 OpenGL,Vulkan 是一个相当低级别的 API,并且提供并行的任务处理。Vulkan 还能够渲染 2D 图形应用程序。除了其较低的 CPU 使用率,Vulkan 还能够更好地在多个 CPU 内核之间分配工作。在功耗、多核优化提升绘图调用上有着非常明显的优势。

而且从性能考虑,「GPU是用户进程唯一可以直接操作的硬件」,是个特例

这与Android 「Client-Sever」 架构不同,一般情况下 应用与硬件交互都需要经过binder委托给系统服务

image.png

软件绘制与硬件绘制

有了GPU,便可以在渲染中加速栅格化的过程,软件绘制往往是cpu借助2d渲染引起skia的能力,去完成渲染,一般实在低端机器

软件绘制使用 Skia 库,它是一款能在低端设备,如手机呈现高质量的 2D 跨平台图形框架,类似 Chrome、Flutter 内部使用的都是 Skia 库

image.png

Android图形系统

有了上面知识之后,我们视角切入到Android系统中来,先来看下Android的图像系统组件 无论开发者使用什么渲染 API,一切内容都会渲染到 「Surface」 上。Surface 表示缓冲区队列中的生产方,而缓冲区队列通常会被 SurfaceFlinger 消耗。在 Android 平台上创建的每个窗口都由 Surface 提供支持。所有被渲染的可见 Surface 都被 SurfaceFlinger 合成到屏幕。

图形架构

下图显示了关键组件如何协同工作 我们可以看到 有关键的两个角色,

  • IMAGE STREAM PRODUCERS(图像生产者)、
  • 另外一个叫IMAGE STREAM CONSUMERS (图像消费者),

整个图形渲染系统就是采用了**「生产者消费者模式」**。屏幕渲染的核心,是对图像数据的生产和消费。 「生产和消费的对象,是BufferQueue 里的 Buffer」

数据流

这是描述Buffer是如何流动起来的,是对上面流程的的数据化抽象

左侧的对象是生成图形缓冲区的渲染器,如主屏幕、状态栏和系统界面。SurfaceFlinger 是合成器,而硬件混合渲染器是混合渲染器。

「BufferQueues 是 Android 图形组件之间的粘合剂」 。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。 抽象成生成消费者模型的 BufferQueue 通信过程 BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。

  • 图像生产方的一些示例包括由相机 HAL 或 OpenGL ES 游戏生成的相机预览。
  • 图像消耗方的一些示例包括 SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。

BufferQueue 是将缓冲区池与队列相结合的数据结构,它使用 Binder IPC 在进程之间传递缓冲区。

Vsync 有两种,Vsync-app 和 Vsync-sf,前者用于告诉 Choreographer,是时候协调 app 生产buffer了;后者用于告诉 SurfaceFlinger,是时候来消费buffer合成并显示到屏幕了

如果把应用程序图形渲染过程当做一次绘画过程,那么绘画过程中 Android 的各个图形组件的作用是:

  • 画笔: Skia 或者 OpenGL。我们可以用 Skia 画笔绘制 2D 图形,也可以用 OpenGL 来绘制2D/3D 图形。正如前面所说,前者使用 CPU 绘制,后者使用 GPU 绘制。
  • 画纸: Surface。所有的元素都在 Surface 这张画纸上进行绘制和渲染。在 Android 中Window 是 View 的容器,「每个窗口都会关联一个 Surface」 。而 「WindowManager 则负责管理这些窗口,并且把它们的数据传递给 SurfaceFlinger」
  • 画板: Graphic Buffer。Graphic Buffer 缓冲用于应用于应用程序图形的绘制,「在 Android4.1 之前使用的是双缓冲机制;在 Android 4.1之后,使用的是三缓冲机制」
  • 显示: SurfaceFlinger。它将 WindowManger 提供的所有 Surface,通过硬件合成器Hardware Composer 合成layer并输出到显示屏。

使用画笔 Skia /OpenGL 将内容绘制到 Surface 上,

「绘制的过程中如果使用 Open GL 渲染,那便是硬件加速」,否则纯靠 CPU 绘制染栅格化的过程就叫软件绘制。对于硬件绘制,我们通过调用 OpenGL ES 接口利用 GPU 完成绘制。

对 BufferQueue 理解了的话,那么可以延伸思考一下:

  • 生产者何时 dequeueBuffer,消费者何时 require Buffer?两者是怎么协调工作的?
  • BufferQueue 里可能会有多少个 Buffer?里面含有不同数目的 Buffer,对 App 的显示工作(具体来说,就是帧率)会有怎样的影响?

要了解这两个问题,就要了解:

  • Choreographer 和 Vsync
  • Double Buffer 和 Triple Buffer

更多精彩内容关注公众号 「Android茶话会」

这在后面会有介绍

渲染系统的持续演进

为了使AndroidUI更加的丝滑,渲染系统也是下足了功夫,在持续演进,

Android 4.0 默认开启硬件加速

  • Android 3.0,API 11开始引入硬件加速

Android 2D 渲染管道支持硬件加速,也就是说,在 View 的画布上执行的所有绘制操作都会使用 GPU。启用硬件加速需要更多资源,因此应用会占用更多内存

  • Android4.0,API 14默认开启硬件加速

由于现在Android4.4以下的手机基本已被淘汰,所以以后的版本默认都是开启了硬件加速。

软件渲染

再没有硬件加速之前主要是通过skia这种软件方式来渲染UI,如下图所示 整个流程如上图所示:

  • 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 的高性能。

硬件加速

Android3.0,支持硬件加速,需要手动打开,Android4.0就默认开启硬件加速了,开启硬件加速流程如下 硬件加速绘制最核心就是通过 GPU 完成 Graphic Buffer 的内容绘制。 在Android 5.0之前,Android应用程序的Main Thread不仅负责用户输入,同时也是一个OpenGL线程,也负责渲染UI。通过引进Render Thread,我们就可以将UI渲染工作从Main Thread释放出来,交由Render Thread来处理,从而也使得Main Thread可以更专注高效地处理用户输入,这样使得在提高UI绘制效率的同时,也使得UI具有更高的响应

此外硬件绘制还引入了一个 DisplayList 的概念,每个 View 内部都有一个DisplayList,当某个 View 需要重绘时,将它标记为 Dirty。当需要重绘时,仅仅只需要重绘一个 View 的 DisplayList,而不是像软件绘制那样需要向上递归。这样可以大大减少绘图的操作数量,因而提高了渲染效率

image.png

Android 4.1黄油计划(Project Butter)

Google 在 2012 年的 I0 大会上宣布了 Project Butter 黄油计划,并且在Android 4.1 中正式开启了这个机制。 Project Butter 主要包含三个组成部分,

  • VSYNC
  • Triple Buffer
  • Choreographer。

其中 VSYNC (Vertical Synchronization) 是理解 Project Butter 的核心

Vsync

对于 Android 4.0 CPU 可能会因为在忙其他的事情,导致没来得及处理 UI 绘制,导致卡顿。 为了解决这个问题,Project Butter 引入了 VSYNC,「它类似于时钟中断,每收到 VSYNC 中断,CPU 会立即准备 Buffer 数据」

由于大部分显示设备刷新频率都是 60 Hz (一秒刷新 60次) ,也就是说一数据的准备都要在 16ms 内完成

这样应用总是在 VSYNC 边界上开始绘制,而 SurfaceFlinger 总是在 VSYNC 边界上进行合成。这样可以消除卡顿,并提升图形的视觉表现。

三缓冲机制(Triple Buffering)

在Android 4.1 之前,Android 使用双缓冲机制。

通常不同的 View 或者Activity 都会共用同一个 Window,也就是共用同一个 Surface。而每个 Surface 都会有一个 BufferQueue 缓存队列,但是这个队列会由 SurfaceFlinger 管理通过匿名共享内存机制与 App 应用层交互

从上图可以看出

  • 每个 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 无法准备下一的数据,如下所示

如果再提供一个缓冲区,CPU、GPU 和显示设备都能使用各自的缓冲区工作,互不影响。 简单来说,三缓冲机制就是在双缓冲机制的基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,

带来的坏处是多使用了一个 Graphic Buffer 所占用的内存。

我们看到,在第一次Jank内,CPU使用了第三块缓冲区,自己计算了C帧的数据,

假如此时没有三缓冲,那么cpu就只能再继续等下一个vsync信号,也就是在图中蓝色A块的地方,才能开始计算C帧数据,就又引发下一次卡顿。

我们看到,「通过引入三缓冲,虽然不能避免卡顿问题,但是却可以大幅优化卡顿问题」 ,尤其是**「避免连续卡顿」,但是,三缓冲也有缺点,就是「内存消耗多」**,所以系统并非一直开启三缓冲,要想真正解决问题,还需要在cpu层对数据尽量优化,从而减小cpu和gpu的计算量,

编舞者Choreographer

这个名字起的非常贴合且很文艺,舞蹈是有节奏的,节奏使舞蹈的每个动作更加协调和连贯; 视图刷新也是如此,Choreographer 可以接收系统的 VSYNC 信号

Choreographer 和 Vsync 共同解决生产者何时生产,消费者何时消费的问题

因此需要一个「协调者」来协调这个工作。 Android 这里的协调者就是 Choreographer------a person who composes the sequence of steps and moves for a performance of dance. Choreographer 协调生产者什么时候去生产------也就是什么时候去绘制一帧。既然要协调,那么肯定是需要有一个协调的依据,这个依据就是 Vsync 信号------也就是垂直同步信号

具体来说是 Vsync_app

Android 4.2 (Jelly Bean)

增加检测过度绘制工具,开发者模式打开检查开关之后,可以通过不同颜色反应过度绘制的程度

Android 5.0 (Lollipop)

RenderThread

最初的 Android 版本里面是没有渲染线程的,渲染工作都是在主线程完成,使用的也都是 CPU ,调用的是 libSkia 这个库, Android5.0之后,进一步提升渲染性能。所有的GL 命令执行都放到单独线程RenderThread(渲染线程) 「中,仅与GPU对话,渲染线程在 RenderNode 中存有染顿的所有信息,可以做些属性动画,这样即便主线程有耗时操作的时候也可以保证动画流程。 是当UI开启硬件加速后,用于分担主线程绘制任务的渲染线程,主线程的 draw 函数并没有真正的执行 drawCall ,而是把要 draw 的内容记录到」** DIsplayList 里面,同步到 RenderThread **中,一旦同步完成,主线程就可以被释放出来做其他的事情,RenderThread 则继续进行渲染工作

Android 7.0 (Nougat)

支持Vulkan

GL的替代品

Android 8.0 (Oreo)

Bitmap内存转移至native内存

  • 2.3之前的像素存储需要的内存是在native上分配的,并且生命周期不太可控,可能需要用户自己回收。
  • 2.3-7.1之间,Bitmap的像素存储在Dalvik的Java堆上,
  • 4.4之前的甚至能在匿名共享内存上分配(Fresco采用),
  • 8.0之后的像素内存又重新回到native上去分配,「不需要用户主动回收」,8.0之后图像资源的管理更加优秀,极大降低了OOM

NativeAllocationRegistry是Android 8.0引入的一种辅助自动回收native内存的一种机制,当Java对象因为GC被回收后,NativeAllocationRegistry可以辅助回收Java对象所申请的native内存

好了,本期就到这里,关注我,精彩继续~

参考

欢迎关注 【Android茶话会】更多精彩等你来探索

  1. 学习之路 取Android技术路线经典电子书
  2. 面试官 取面试官经典面试题
  3. 技术简历 取精选简历模板一份
相关推荐
吾日三省吾码4 小时前
JVM 性能调优
java
Estar.Lee4 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh5 小时前
uiautomator案例
android
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi776 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
工业甲酰苯胺6 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3436 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀6 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20206 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea