• 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 及以上版本)缓 冲区,刷新机制中的双缓冲和三重缓冲技术。
  • 最后总结起来显示整体流程分为三个模块:应用层绘制到缓存区,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 上有层叠太多的绘制单元; 还有可能是动画执行的次数过多。

更多相关文章

  1. Android群英传笔记——第七章:Android动画机制和使用技巧
  2. Android(安卓)bitmap图片处理
  3. Android(安卓)开发者的 Flutter(三) —— Flutter 中的动画与绘制
  4. Android开发者面试一百题
  5. android + red5 + rtmp
  6. Android消息机制之ThreadLocal浅析
  7. 我要做 Android(安卓)之 数据持久化
  8. Android数据存储方案ContentProvider存储数据
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. 使用Android design support library在Ec
  2. Android build.prop生成过程
  3. 【Android(安卓)界面效果28】Android应用
  4. Android(安卓)使用 WebView 实现 Hybrid
  5. 仿新浪微博布局学习——妙用TabHost
  6. pAdTy_-8 支持不同的设备
  7. Android(安卓)在一个程序中启动另一个程
  8. android:phoneNumber 与 android:autoLin
  9. Android恶意样本批量获取方法
  10. 正则表达式之JSP、Android