01-Android底层原理|Android的各个渲染框架和Android图层渲染原理

5,359 阅读18分钟

前言

Android

Android 系统 由美国的Google公司发型。它作为智能移动终端搭载系统之一,自诞生以来,就备受关注。至今为止已经迭代了9+代了,国内不少手机开发厂商(如小米)等都是基于Android 系统进行改造,然后搭载在自己的产品中去的

Android 底层内核空间以 【Linux Kernel 】作为基石。上层用户空间由 Native系统库虚拟机运行环境框架层组成,通过系统调用(Syscall)连通系统的【内核空间】与【用户空间】。
对于用户空间主要采用 C++ 和 Java 代码编写,通过 JNI技术 打通用户空间的 Java层Native层(C++/C) ,从而连通整个系统。

我们今天就Android页面渲染原理为主题,对Android系统的渲染框架和渲染流水线展开讨论,以为后期在项目实施过程中做技术选型做知识储备!!那就让我们进入今天的正题吧!!!

概述

本文主要对Android页面渲染原理展开讨论。在讨论本文主题之前,我们需要进行一定的知识铺垫。先带大家简单回顾一下计算机图形渲染原理。若是您有一定的`计算机图形学基础,可以忽略前期的知识准备直接从本文的第二节开始阅读。

本文总共有以下几个章节:
一、铺垫知识

一、铺垫知识

安卓系统的图形渲染原理其实在核心部分都是和计算机图形学的计算机图形渲染原理原理一样的。所以我们在了解安卓的视图系统和其2D、3D渲染框架渲染流水线之前,我们需要进入我的这篇文章:计算机图形渲染原理进行一定的知识准备。

链接附带的文章中,我们可以了解到“智能硬件CPUGPU的设计理念以及两者之间的性能差异”、“计算机图形渲染芯片GPU的诞生史”、"围绕GPU工作的3D图形渲染库(OpenGL、DirectX等)、图形学相关的专业术语和OpenGL工作的渲染流水线"、“屏幕成像的电子束CRT扫描原理”、“屏幕成像原理”等诸多相关的核心要点。

您若是不想关注CPU、GPU。直接了解移动设备的屏幕成像原理也可以阅读我这一份专门为移动而写的简约版:移动终端屏幕成像与卡顿
在这篇文章中,我们可以分别从两个维度去关注,第一个就是系统成像遇到的Bug问题,第二个就是解决问题的解决方案. 几个要点可以简单的归纳为:

  • 问题:"屏幕撕裂Screen Tearing""掉帧Jank"视图成像切换衔接失误导致的画面空白
  • 解决方案:"Vsync""Double Buffering""Triple Buffering"

总结,我们这里主要关注屏幕成像的整个渲染流水线,以便于我们后面对安卓的图像渲染原理展开讨论:
①获取图层渲染数据→②GPU加工成像素数据→③帧缓冲器(存储像素信息)→④视频控制器读取缓存→数→⑤数模转换、显示器显示

我们今天的主题就是主要关注第一个环节。入手点分为几个:

  • 安卓系统的视图层(Layer)和视图窗口(Window)以及安卓系统中的各个图形渲染框架(2D/3D)
  • 安卓系统的渲染流水线
  • 安卓系统的事件机制

二、安卓的视图层和视图窗口

2.1 安卓显示系统的三驾马车:View,AMS,WMS

在开发过程中,我们编码绘制界面时,我们只关心单个界面,但是运行工程,项目在设备上显示的时候,可能就有多个界面了。
做过Android开发的同学应该都知道,我们需要继承一个Activity,用Activity呈现我们的界面。Activity的职责之一是对界面生命周期的管理,这也就伴随了对视图窗口的管理。这中间就涉及了两个Android中两个主要的服务,AMS(ActivityManagerService)和WMS(WindowManagerService)
View,AMS,WMS可以说是整个上层显示系统的三驾马车

在Android中,一个窗口用一个Surface描述。多个窗口(窗口不一定都是Activity),需要同时显示,我们就需要将多个窗口进行合并。这就需要显示系统中重量级的服务SurfaceFlinger,Surfaceflinger控制窗口的合成,将多个窗口合并成一个,再送到LCD。

Surfaceflinger是Native的服务,Surfaceflinger中怎么去描述一个窗口呢?Surfaceflinger采用图层的概念,即Layer。SurfaceFlinger合成,就是基于Display HAL的实现,将多个Layer合并。关于Display HAL,各个厂商的实现就千差万别了。

2.2 SurfaceUI

总的来说 Android 系统采用一种称为 Surface 的 UI 架构为应用程序提供用户界面。

  • 在 Android 应用程序中,每一个 Activity 组件都关联有一个或者若干个窗口每一个窗口都对应有一个 Surface 界面很简单,拆开来看,包含微信、悬浮工具箱、通知栏、底部虚拟按键栏。
    上层每一个界面,其实都对应SufaceFlinger里的一个Surface对象,上层将自己的内容绘制在对应的Surface内,
  • 有了这个 Surface 之后,应用程序就可以在上面渲染窗口的 UI。最终这些已经绘制好了的 Surface 都会被统一 提交给 Surface 管理服务 SurfaceFlinger 进行合成,最后显示在屏幕上面。
    • 接着,SufaceFlinger需要将所有上层对应的Surface内的图形进行合成,具体看下图:
    1. 对应上层的一个Window(对话框、Activity、状态栏)
    2. 作为上层图形绘制的画板
    3. Canvas是画笔,上层通过调用Canvas的API向Surface上绘制图形
    4. Surface内部存在多个缓冲区,形成一个BufferQueue
  • 无论是应用程序,还是 SurfaceFlinger,都可以利用 GPU 等硬件来进行 UI 渲染,以便获得更流畅的 UI。

一句话来概括一下 Android 应用程序显示的过程就是:

Android 应用程序调用 SurfaceFlinger 服务 把 "经过测量、布局和绘制后的 Surface" 通过"图形渲染框架" 借助 GPU 渲染,最后数模转换显示到屏幕上。

2.3 安卓图形界面的背后重要成员

我们先了解几个重要的成员:

  • Surface: Android 应用的每个窗口对应一个画布(Canvas),即 Surface

    • 可以理解为 Android 应用程序的一个窗口。
    • Surface 是一个接口,供生产方与使用方交换缓冲区。
  • SurfaceView: SurfaceView 是一个组件,可用于在 View 层次结构中嵌入其他合成层。

    • SurfaceView 采用与其他 View 相同的布局参数,因此可以像对待其他任何 View 一样对其进行操作,但 SurfaceView 的内容是透明的。
    • 当 SurfaceView 的 View 组件即将变得可见时,框架会要求 SurfaceControl 从 SurfaceFlinger 请求新的 Surface。
  • Surface Buffer: 图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装。

    • 也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?
    • Surface内部提供一个BufferQueue,与上层和SurfaceFlinger形成一个生产者消费者模型,上层对应Producer,SurfaceFlinger对应Consumer。
    • 三者通过Buffer产生联系,每个Buffer都有四种状态:
      • Free:可被上层使用
      • Dequeued:出列,正在被上层使用
      • Queued:入列,已完成上层绘制,等待SurfaceFlinger合成
      • Acquired:被获取,SurfaceFlinger正持有该Buffer进行合成
    • Buffer的一次转移过程大致为:
      • 从BufferQueue转移到上层
      • 上层绘制完成再放回BufferQueue
      • 接着SurfaceFlinger再拿去合成
      • 最后又放回BufferQueue
      • 如此循环,形成一个Buffer被循环使用的过程。
  • EGLSurface 和 OpenGL ES: OpenGL ES (GLES) 定义了用于与 EGL 结合使用的图形渲染 API。

    • EGI 是一个规定如何通过操作系统创建和访问窗口的库
    • 要绘制纹理多边形的时候使用 GLES 调用;
    • 要将渲染放到屏幕上的时候使用 EGL 调用;
  • WindowManager: WindowManager 会控制窗口对象,它们是用于容纳视图对象的容器。

    • 窗口对象始终由 Surface 对象提供支持。
    • WindowManager 会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z 轴顺序以及窗口的许多其他方面。
    • WindowManager 会将所有窗口元数据发送到 SurfaceFlinger,以便 SurfaceFlinger 可以使用这些数据在屏幕上合成 Surface。
  • SurfaceFlinger: Android 系统服务。 负责管理 Android 系统的帧缓冲区。

    1. 分配图形缓冲区
    2. 合成图形缓冲区
    3. 管理 VSYNC 事件
    • SurfaceFlinger 接受来自多个源的数据缓冲区,然后将它们进行合成并发送到显示屏。
    • WindowManager 为 SurfaceFlinger 提供缓冲区和窗口元数据,而 SurfaceFlinger 可使用这些信息将 Surface 合成到屏幕
    • SurfaceFlinger 可通过两种方式接受缓冲区:通过 BufferQueue 和 SurfaceControl,或通过 ASurfaceControl。
  • ViewRootImpl: 用来控制窗口的渲染,以及用来与 WindowManagerServiceSurfaceFlinger 通信。

  • BufferQueue: BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方)。

    • 几乎所有在系统中移动图形数据缓冲区的内容都依赖于 BufferQueue。
  • View和ViewGroup

    • View是Android中所有控件的基类,不管是简单的TextView,Button还是复杂的LinearLayout和ListView,它们的共同基类都是View。它表示一个空白的矩形区域。
    • View类还有一个很重要的子类:ViewGroup,但是ViewGroup通常作为其他组件的容器使用。
    • Android的所有UI组件都是建立在View、ViewGroup基础之上的,Android采用了“组合器”模式来设计View和ViewGroup:ViewGroup是View的子类,因此ViewGroup也可以被当做View使用。
    • 对于一个Android应用的图形用户界面来说,ViewGroup作为容器来盛装其他组件,而ViewGroup里除了可以包含普通View组件之外,还可以再次包含ViewGroup组件
    • 在View中,具备它的位置、颜色等属性。同时也管理着点击、触摸手势等事件机制
    • 我们可以引入一张图,来理解一下从View到图形渲染框架再到Surface的关系
  • Canvas: Canvas是一个2D图形API,是Android View树实际的渲染者。

    • Canvas又可分为Skia软件绘制和hwui硬件加速绘制。

    • Android4.0之前默认是Skia绘制,该方式完全通过CPU完成绘图指令,并且全部在主线程操作,在复杂场景下单帧容易超过16ms导致卡顿。

    • 从Android4.0开始,默认开启硬件加速渲染,而且5.0开始把渲染操作拆分到了两个线程:

      • 主线程和渲染线程,主线程负责记录渲染指令
      • 渲染线程负责通过OpenGL ES完成渲染,两个线程可以并发执行。
    • 除了Canvas,开发者还可以在异步线程直接通过OpenGL ES进行渲染,一般适用于游戏、视频播放等独立场景。

  • OpenGL ES OpenGL是 c-based 的3D渲染API

  • Vulkan: Vulkan 是一种用于高性能 3D 图形的低开销、跨平台 API。

    • 与 OpenGL ES 一样,Vulkan 提供用于在应用中创建高质量实时图形的工具。
    • VulKan是用来替换OpenGL的。它不仅支持3D,也支持2D,同时更加轻量级
  • Skia Skia 是Android底层的2D图形库

三、安卓系统的各个渲染框架和渲染流水线

3.1 安卓渲染的演变

了解Android系统对渲染的不断优化历史,对于理解渲染很有帮助。

Android 4.1

引入了project butter黄油计划:Vsync、三倍缓冲、choreography编舞者。

Android 5.0

引入了RenderThread线程(该线程是系统在framework层维护),把之前CPU直接操作绘制指令(OpenGL/vulkan/skia)部分,交给了单独的渲染线程。减少主线程的工作。即使主线程卡住,渲染也不受影响。

Android 7.0

引入了Vulkan支持。 OpenGL是3D渲染API,VulKan是用来替换OpenGL的。它不仅支持3D,也支持2D,同时更加轻量级。

3.2 图形渲染框架:OpenGL、Vulkan、Skia

  • OpenGL: 是一种跨平台的3D图形绘制规范接口。OpenGL EL则是专门针对嵌入式设备,如手机做了优化。

  • Vulkan: 跟OpenGL相同功能,不过它同时支持3D、2D,比OpenGL更加的轻量、性能更高。

  • Skia: skia是图像渲染库,2D图形绘制自己就能完成。3D效果(依赖硬件)由OpenGL、Vulkan、Metal支持。它不仅支持2D、3D,同时支持CPU软件绘制和GPU硬件加速。Android、flutter都是使用它来完成绘制。

3.3 图形渲染流水线

在介绍各个渲染框架协作下的渲染流水线之前,先引入一张官方系统的图:

这幅图大致描述了图形数据的流转:

  • OpenGL ES、MediaPlayer等生产者生产图形数据到Surface
  • Surface通过IGraphicBufferProducerGraphicBuffer跨进程传输给消费者SurfaceFlinger
  • SurfaceFlinger根据WMS提供的窗口信息合成所有的Layer(对应于Surface),具体的合成策略由hwcomposerHAL模块决定并实施,
  • 最后也是由该模块送显到Display,而Gralloc模块则负责分配图形缓冲区。

图中概念的解释:

  • Image Stream Producers(图形流的生产者): 可产生graphic buffers的生产者. 例如OpenGL ES, Canvas 2D, mediaserver的视频解码器.

  • Image Stream Consumers(图形流的消费者): 最场景的消费者便是SurfaceFlinger,它使用OpenGL和Hardware Composer来组合一组surfaces.

    • OpenGL ES应用能消费图形流, 比如camera app消费camera预览图形流;
    • 非OpenGL ES应用也能消费, 比如ImageReader类
  • Window Manager: 用于管理window, 这是一组view的容器. WM将手机的window元数据(包括屏幕放心,z-order等)信息发送给SurfaceFlinger,因此SurfaceFlinger 能使用这些信息来合成surfaces,并输出到显示设备.

  • Hardware Composer(硬件混合渲染器): 这是显示子系统的硬件抽象层。

    • 显示子系统的硬件抽象实现。SurfaceFlinger 可以将某些合成工作委托给 Hardware Composer,以分担 OpenGL 和 GPU 上的工作量。
    • SurfaceFlinger 只是充当另一个 OpenGL ES 客户端。因此,在 SurfaceFlinger 将一个或两个缓冲区合成到第三个缓冲区中的过程中,它会使用 OpenGL ES。这样使合成的功耗比通过 GPU 执行所有计算更低。
  • HAL: 硬件抽象层。把图形数据展示到设备屏幕

    • Hardware Composer HAL 则进行另一半的工作,并且是所有 Android 图形渲染的核心。
    • Hardware Composer 必须支持事件,其中之一是 VSYNC(另一个是支持即插即用 HDMI 的热插拔)
  • Gralloc: 全称为graphics memory allocator,图像内存分配器, 用于图形生产这来请求分配内存.

从这张图我们可以知道,安卓图形渲染设计理念有一个生成者消费者模式,而他们中间的桥梁就是Buffer Data:

生产者和消费者运行在不同的进程.

  • 生产者请求一块空闲的缓存区:dequeueBuffer()
  • 生产者填充缓存区并返回给队列: queueBuffer()
  • 消费者获取一块缓存区: acquireBuffer()
  • 消费者使用完毕,则返回给队列: releaseBuffer()

我们前面引入那张官方图我们可以把它变成有层次感的另一幅图来理解一下渲染过程和其中涉及到的几个图形框架:

大体上,应用开发者可以通过两种方式将图像绘制到屏幕上:

  • Canvas
  • OpenGL ES

Canvas是一个2D图形API,是Android View树实际的渲染者。

  • Canvas又可分为Skia软件绘制和Hwui硬件加速绘制。
  • Android4.0之前默认是Skia绘制,该方式完全通过CPU完成绘图指令,并且全部在主线程操作,在复杂场景下单帧容易超过16ms导致卡顿。
  • 从Android4.0开始,默认开启硬件加速渲染,而且5.0开始把渲染操作拆分到了两个线程:主线程和渲染线程,主线程负责记录渲染指令,渲染线程负责通过OpenGL ES完成渲染,两个线程可以并发执行。

除了Canvas,开发者还可以在异步线程直接通过OpenGL ES进行渲染,一般适用于游戏、视频播放等独立场景。

从应用侧来看,不管是Canvas,还是OpenGL ES,最终渲染到的目标都是Surface。现在比较流行的跨平台UI框架Flutter在Android平台上也是直接渲染到Surface。

  • Surface是一个窗口,例如:一个Activity是一个Surface、一个Dialog也是一个Surface,承载了上层的图形数据,与SurfaceFlinger侧的Layer相对应。

    • Native层Surface实现了ANativeWindow结构体,在构造函数中持有一个IGraphicBufferProducer,用于和BufferQueue进行交互。

    • BufferQueue是连接Surface和Layer的纽带,当上层图形数据渲染到Surface时,实际是渲染到了BufferQueue中的一个GraphicBuffer,然后通过IGraphicBufferProducerGraphicBuffer提交到BufferQueue,让SurfaceFlinger进行后续的合成显示工作。

    • SurfaceFlinger负责合成所有的Layer并送显到Display,这些Layer主要有两种合成方式:

      • OpenGL ES:把这些图层合成到FrameBuffer,然后把FrameBuffer提交给hwcomposer完成剩余合成和显示工作。
      • hwcomposer:通过HWC模块合成部分图层和FrameBuffer,并显示到Display。

至于OpenGL ES 或者Skia 它的详细工作流程,本文不铺展开来讨论,若是希望了解可以关注我的相关阅读推荐阅读

四、总结

本文简单介绍了 安卓系统的 UI组成 和背后的一些相关概念。简单介绍了几个图形渲染框架和渲染流程。

本文尚未深入讨论的话题有:

  1. 在渲染过程中,SurfaceFlinger的工作原理
  2. 渲染过程中,UI线程、RenderThread线程、SurfaceFlinger之间的数据如何传递
  3. 对图层渲染框架SkiaOpenGLESVulkan的使用
  4. ......

推荐阅读

相关阅读(共计14篇文章)

iOS相关专题

webApp相关专题

跨平台开发方案相关专题

阶段性总结:Native、WebApp、跨平台开发三种方案性能比较

Android、HarmonyOS页面渲染专题

小程序页面渲染专题

总结