Android 绘制原理浅析

  • Android 的显示过程可以简单概括为:Android 应用程序把经过测量、布局、绘制后的surface 缓存数据,通过 SurfaceFlinger 把数据渲染到显示屏幕上,通过 Android 的刷新机制 来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要 绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕。 通过阅读 Android 系统的源码可以了解显示的流程,Android 的图形显示系统采用的是 Client/Server 架构。SurfaceFlinger(Server)由 C++代码编写。Client 端代码分为两部分,一 部分是由 Java 提供给应用使用的 API,另一部分则是由 C++写成的底层具体实现。

绘制原理

  • 绘制任务是由应用发起的,最终通过系统层绘制到硬件屏幕上,也就是说,应用进程绘 制好后,通过跨进程通信机制把需要显示的数据传到系统层,由系统层中的 SurfaceFlinger 服务绘制到屏幕上。那么应用层和系统层中的流程是什么样的呢?
应用层
  • 先来看一个 UI 界面的典型构成框架,也可以是一个 Activity 的构成。如图 2-2 所示,有 很多不同层次的基本元素——View,整体是一个树型结构,有不同的嵌套,存在着父子关系, 子 View 在父 View 中,这些 View 都经过一个相同的流程最终显示到屏幕上,这也意味着要 完整地显示所有数据,就要对其中的 View 都进行一次绘制工作,并且针对每个 View 的操作 都是一个递归过程。

  • 在 Android 的每个 View 绘制中有三个核心步骤(见图 2-3),通过 Measure 和 Layout 来 确定当前需要绘制的 View 所在的大小和位置,通过绘制(Draw)到 surface,在 Android 系 统中整体的绘图源码是在 ViewRootImp 类的 performTraversals()方法,通过这个方法可以 看出 Measure 和 Layout 都是递归来获取 View 的大小和位置,并且以深度作为优先级。可以 看出,层级越深,元素越多,耗时也就越长。

    View绘制流程可以看这篇文章

  • 页面构成框架
    在这里插入图片描述

  • View的绘制流程
    在这里插入图片描述
    (1)Measure 用深度优先原则递归得到所有视图(View)的宽、高;获取当前 View 的正确宽度 childWidthMeasureSpec 和高度 childHeightMeasureSpec 之后,可以调用它的成员函数 Measure 来设置它的大小。如果当前正在测量的子视图 child 是一个视图容器,那么它又会 重复执行操作,直到它的所有子孙视图的大小都测量完成为止。
    (2)Layout 用深度优先原则递归得到所有视图(View)的位置;当一个子 View 在应用程序窗口左 上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在 应用程序窗口中的布局。
    (3)Draw 目前 Android 支持了两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加 速在 Android 3.0 开始已经全面支持,很明显,硬件加速在 UI 的显示和绘制的效率远远高于 CPU 绘制,但硬件加速并非如大家所想的那么完善,它也存在明显的缺点: ·耗电:GPU 的功耗比 CPU 高。 ·兼容问题:某些接口和函数不支持硬件加速。 ·内存大:使用 OpenGL 的接口至少需要 8MB 内存。 所以是否使用硬件加速,需要考虑一些接口是否支持硬件加速,同时结合产品的形态和 平台,比如 TV 版本就不需要考虑功耗的问题,而且 TV 屏幕大,使用硬件加速容易实现更 好的显示效果。

系统层
  • 真正把需要显示的数据渲染到屏幕上,是通过系统级进程中的 SurfaceFlinger 服务来实 现的,SurfaceFlinger 的主要工作如下:
  1. 响应客户端事件,创建 Layer 与客户端的 Surface 建立连接。
  2. 接收客户端数据及属性,修改 Layer 属性,如尺寸、颜色、透明度等。
  3. 将创建的 Layer 内容刷新到屏幕上。
  4. 维持 Layer 的序列,并对 Layer 最终输出做出裁剪计算
  • 既然是两个不同进程,那么肯定需要一个跨进程的通信机制来实现数据传输,在 Android 的显示系统,使用了 Android 的匿名共享内存:SharedClient,每一个应用和 SurfaceFlinger 之间都会创建一个 SharedClient,如下图所示。从图中可以看出,在每个 SharedClient 中,最多可以创建 31 个 SharedBufferStack,每个 Surface 都对应一个 SharedBufferStack,也 就是一个 window。 一个 SharedClient 对应一个 Android 应用程序,而一个 Android 应用程序可能包含多个窗 口,即 Surface。也就是说 SharedClient 包含的是 SharedBufferStack 的集合。因为最多可以创 建 31 个 SharedBufferStack,这也意味着一个 Android 应用程序最多可以包含 31 个窗口,同 时每个 SharedBufferStack 中又包含了两个(低于 4.1 版本)或者三个(4.1 及以上版本)缓 冲区,刷新机制中的双缓冲和三重缓冲技术。
    Android 显示框架
  • 最后总结起来显示整体流程分为三个模块:应用层绘制到缓存区,SurfaceFlinger 把缓存 区数据渲染到屏幕,由于是两个不同的进程,所以使用 Android 的匿名共享内存 SharedClient 缓存需要显示的数据来达到目的。 SurfaceFlinger 把缓存区数据渲染到屏幕(流程如下图所示),主要是驱动层的事情,这 里不做太多解释。
  • 从图中可以看出,绘制过程首先是 CPU 准备数据,通过 Driver 层把数据交给 CPU 渲 染,其中 CPU 主要负责 Measure、Layout、Record、Execute 的数据计算工作,GPU 负责 Rasterization(栅格化)、渲染。由于图形 API 不允许 CPU 直接与 GPU 通信,而是通过中间 的一个图形驱动层(Graphics Driver)来连接这两部分。图形驱动维护了一个队列,CPU 把 display list 添加到队列中,GPU 从这个队列取出数据进行绘制,最终才在显示屏上显示出来。

在这里插入图片描述

  • 知道了绘制的原理后,那么到底绘制一个单元多长时间才是合理的,首先需要了解一个 名词:FPS。FPS(Frames Per Second)表示每秒传递的帧数。在理想情况下,60 FPS 就感觉 不到卡,这意味着每个绘制时长应该在 16ms 以内,如图
    在这里插入图片描述
  • 但是 Android 系统很有可能无法及时完成那些复杂的界面渲染操作。Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅的 画面所需的 60FPS。即为了实现 60FPS,就意味着程序的大多数绘制操作都必须在 16ms 内 完成。 如果某个操作花费的时间是 24ms,系统在得到 VSYNC 信号时就无法进行正常渲染,这 样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面。主要场景在执行动画或 者滑动 ListView 时更容易感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的 现象,从而感觉卡顿。有很多原因可以导致 CPU 或者 GPU 负载过重从而出现丢帧现象:可 能是你的 Layout 太过复杂,无法在 16ms 内完成渲染;可能是 UI 上有层叠太多的绘制单元; 还有可能是动画执行的次数过多。
发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/98646852
今日推荐