Android的显示过程可以概括为:
Android应用程序把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据。即应用侧绘制、系统侧渲染,通过进程间通信(IPC)把应用层需要绘制的数据传递到系统层服务,系统层服务通过显示刷新机制把数据更新到屏幕。

(两个部分(应用侧绘制、系统侧渲染)、两个机制(进程间通讯机制、显示刷新机制))

接下来分别从 应用层、系统层和刷新机制三个方面来介绍下Android系统的显示原理。

SurfaceFlinger:Android系统服务,负责管理Android系统的帧缓冲区,即显示屏幕。

Surface:Android应用的每个窗口对应一个画布(Canvas),即Surface,可以理解为Android应用程序的一个窗口。

应用层
我们都知道一个Android的UI界面layout是整体一棵由很多不同层次的View组成的树形结构,它们存在着父子关系,子View在父View中,这些View都经过一个相同的流程最终显示到屏幕上。

在Android中每个View的绘制中有三个核心步骤,通过Measure和Layout来确定当前需要绘制的View所在的大小和位置,通过绘制(Draw)到surface。

1)Measure

用深度优先原则递归得到所有View的宽、高;获取当前View的正确宽度childWidthMeasureSpec和childHeightMeasureSpec之后,可以调用它的成员函数Measure来设置它的大小。若子View是一个ViewGroup,那么它又会重复执行操作,直到它的所在子孙View的大小都测量完成为止。

2)Layout

用深度优先原则递归得到所有View的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。

3)Draw

Android支持两种绘制方式:软件绘制(CPU)和硬件绘制(GPU,要求>= Android3.0)。

在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:

1.      让View层次结构失效

2.      绘制View层次结构

当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate()方法。无效(invalidation)消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。不幸的是,这种方法有两个缺点:

1.      绘制了不需要重绘的视图(与脏区域相交的区域)

2.      掩盖了一些应用的bug(由于会重绘与脏区域相交的区域)

    注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。

 

在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:

1.      让View层次结构失效

2.      记录、更新显示列表

3.      绘制显示列表

这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象。但Android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。另一个优化是,Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象则能重放先前显示列表记录的绘制指令来进行简单的重绘工作。

使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,以便下次重用,然后再调用OpenGL完成绘制。

硬件加速在UI的显示和绘制的效率远远高于CPU绘制,硬件加速提高了Android系统显示和刷新的速度,但它也不是万能的,它有三个缺陷:

耗电:GPU功耗比CPU高;
兼容问题:某些接口和函数不支持硬件加速;
内存大:使用OpenGL的接口至少需要8MB内存。

系统层
Android是通过系统级进程中的SurfaceFlinger服务来把真正需要显示的数据渲染到屏幕上。SurfaceFlinger服务使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。

SurfaceFlinger的主要工作是:

响应客户端事件,创建Layer与客户端的Surface建立连接。
接收客户端数据及属性,修改Layer属性,如尺寸、颜色、透明度等。
将创建的Layer内容刷新到屏幕上。
维持Layer的序列,并对Layer最终输出做出裁剪计算。

因应用层和系统层分别是两个不同进程,需要一个跨进程的通信机制来实现数据传输,在Android的显示系统中,使用了Android的匿名共享内存:SharedClient。每一个应用和SurfaceFlinger之间都会创建一个SharedClient,每个SharedClient中,最多可以创建31个SharedBufferStack,每个Surface都对应一个SharedBufferStack,也就是一个window。这意味着一个Android应用程序最多可以包含31个窗口,同时每个SharedBufferStack中又包含两个(<4.1)或三个(>=4.1)缓冲区。

总结:应用层绘制到缓冲区,SurfaceFlinger把缓存区数据渲染到屏幕,两个进程之间使用Android的匿名共享内存SharedClient 缓存需要显示的数据。

显示刷新机制
Android系统一直在不断的优化、更新,但直到4.0版本发布,有关UI显示不流畅的问题仍未得到根本解决。

从Android4.1版本开始,Android对显示系统进行了重构,引入了三个核心元素:VSYNC, Tripple Buffer和Choreographer。VSYNC(垂直同步) 是Vertical Synchronized的缩写,是一种定时中断;意思是显卡输出频率与屏幕刷新频率同步的意思。Tripple Buffer是显示数据的缓冲区;Choreographer起调度作用,将绘制工作统一到VSYNC的某个时间点上,使应用的绘制工作有序进行。

Android在绘制UI时,会采用一种称为“双缓冲”的技术,双缓冲即使用两个缓冲区(在SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。理想情况下,一个刷新会在16ms内完成(60FPS),下图就是描述的这样一个刷新过程(Display处理前Front Buffer,CPU、GPU处理Back Buffer。

1.没有VSYNC信号同步时

但实际运行时情况并不一定如此

1)第一个16ms开始:Display显示第0帧,CPU处理完第一帧后,GPU紧接其后处理继续第一帧。三者都在正常工作。

2)进入第二个16ms:因为早在上一个16ms时间内,第1帧已经由CPU,GPU处理完毕。故Display可以直接显示第1帧。显示没有问题。但在本16ms期间,CPU和GPU却并未及时去绘制第2帧数据(前面的空白区表示CPU和GPU忙其它的事),直到在本周期快结束时,CPU/GPU才去处理第2帧数据。

3)进入第三个16ms,此时Display应该显示第2帧数据,但由于CPU和GPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次(对应时间段上标注了一个Jank),导致错过了显示第二帧。

通过上述分析可知,此处发生Jank的关键问题在于,为何第1个16ms段内,CPU/GPU没有及时处理第2帧数据?原因很简单,CPU可能是在忙别的事情,不知道该到处理UI绘制的时间了。可CPU一旦想起来要去处理第2帧数据,时间又错过了。 为解决这个问题,Android 4.1中引入了VSYNC,核心目的是解决刷新不同步的问题。

2.引入VSYNC信号同步后

在加入VSYNC信号同步后,每收到VSYNC中断,CPU就开始处理各帧数据。已经解决了刷新不同步的问题。

但是上图中仍然存在一个问题:CPU和GPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPU的FPS(帧率)要高于Display的FPS。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与Display的FPS相同。但这种处理并没有什么问题,因为Android设备的Display FPS一般是60,其对应的显示效果非常平滑。

但如果CPU/GPU的FPS小于Display的FPS,情况又不同了,将会发生如下图的情况:

1)在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。

2)同理,在第二个16ms时间段内,CPU无所事事,因为A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦过了VSYNC时间点,CPU就不能被触发以处理绘制工作了。

为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer(Android 4.1之前)。如果有第三个Buffer的存在,CPU就能直接使用它,而不至于空闲。于是在Android4.1以后,引出了第三个缓冲区:Tripple Buffer。Tripple Buffer利用CPU/GPU的空闲等待时间提前准备好数据,并不一定会使用。

3.引入Tripple Buffer后

引入Tripple Buffer后的刷新时序如下图:

上图中,第二个16ms时间段,CPU使用C Buffer绘图。虽然还是会多显示A帧一次,但后续显示就比较顺畅了。

是不是Buffer越多越好呢?回答是否定的。由上图可知,在第二个时间段内,CPU绘制的第C帧数据要到第四个16ms才能显示,这比双Buffer情况多了16ms延迟。所以缓冲区并不是越多越好。

总结
从Android系统的显示原理可以看到,影响绘制的根本原因有以下两方面:

1.绘制任务太重,绘制一帧内容耗时太长。(有时是UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域。)

2. 动画使用次数过多。

通常情况下Android需要将xml布局文件转换成GPU可以识别的绘制对象,而这些绘制对象被存放到DisplayList的数组中,当view第一次绘制的时候DisplayList被创建,view第二次绘制的时候GPU就直接从DisplayList获取绘制对象,省去了Measure,Layout的时间,但是如果我们改变了view的绘制内容那么得重新Measure,layout,DisplayList也会被重新创建,这么看来动画效果是非常消耗性能的。因为它必须经过多次重绘。

3.主线程太忙了,导致VSYNC信号来时还没有准备好数据导致丢帧。

实际开发中,我们应该只在主线程做以下几方面的工作:

UI生命周期控制
系统事件处理
消息处理
界面布局
界面绘制
界面刷新
除了这些以外,尽量避免将其它处理放到主线程中,特别是复杂的数据计算和网络请求。

-------------------------------------------------------------------------------------------

---------------------
作者:Android面试专栏
来源:CSDN
原文:https://blog.csdn.net/blessed24/article/details/74277933
版权声明:本文为博主原创文章,转载请附上博文链接!

Android系统显示原理的更多相关文章

  1. Android系统Root原理初探(转)

    http://www.imooc.com/learn/126 chkconfig setup 解压update.zip这个文件,可发现它一般打包了如下这几个文件: 或者没有updates而是syste ...

  2. Android 显示原理简介

    作者:yearzhu,2011年进入腾讯公司,从事过Web端及移动端的测试工作,喜爱新鲜事物及新技术,目前在SNG开放平台测试组负责的移动互联SDK的测试工作. 现在越来越多的应用开始重视流畅度方面的 ...

  3. Android View绘制和显示原理简介

    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...

  4. 深入浅出 - Android系统移植与平台开发(五)- 定制手机模拟器ROM

    一. 修改化定制Android4.0系统 Android系统启动时,先加载Linux内核,在Linux的framebuffer驱动里可以定制开 机界面,Linux内核启动成功后,挂载根文件系统,启动A ...

  5. android系统平台显示驱动开发简要:LCD基本原理篇『一』

    平台信息:内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博客 ...

  6. Android系统Recovery工作原理之使用update.zip升级过程---updater-script脚本语法简介以及执行流程(转)

    目前update-script脚本格式是edify,其与amend有何区别,暂不讨论,我们只分析其中主要的语法,以及脚本的流程控制. 一.update-script脚本语法简介: 我们顺着所生成的脚本 ...

  7. Android系统Recovery工作原理之使用update.zip升级过程分析(九)---updater-script脚本语法简介以及执行流程【转】

    本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465603       Android系统Recovery工作原理之使用update.zip ...

  8. Android系统Recovery工作原理之使用update.zip升级过程分析(八)---解析并执行升级脚本updater-script【转】

    本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465551  Android系统Recovery工作原理之使用update.zip升级过程分 ...

  9. Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节【转】

    本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465439  Android系统Recovery工作原理之使用update.zip升级过程分 ...

随机推荐

  1. SSL 链接安全协议的enum

    摘自:https://blog.csdn.net/lan_liang/article/details/70948221 在进行HTTPS连接时,需要指定SecurityProtocol.对于.NET ...

  2. Lazarus下改变DBGrid记录的颜色,与Delphi不同了。

    功能:根据一条记录满足特定条件,使用不同的颜色进行显示. 来源:http://www.aliyagoo.com/blog 主要事件是在PrepareCanvas 不是DrawColumnCell 而且 ...

  3. linux 2.6.32文件系统的dentry父子关系

    我们知道,linux文件系统,inode和dentry是有对应关系的,dentry是文件名或者目录的一个管理结构,2.6内核中: struct dentry { atomic_t d_count; u ...

  4. 【383】defaultdict 相关用法

    可以定义一个字典,可以添加默认值,int 为 0,list 为 [],set 为 {} int:默认值为 0 from collections import defaultdict int_dict ...

  5. 记Dagger2使用过程中的一个BUG--compileGoogleDebugJavaWithJavac

    项目编译可以通过,不过没有生成Dagger2的类,导致无法运行项目.. 错误提示 Error:(14, 41) 错误: 找不到符号 符号: 类 DaggerAppComponent 位置: 程序包 c ...

  6. Idea使用Maven异常 --- Maven网络代理设置

    在conf/setting.xml和m2/repository/setting.xml中加入: <proxies> <!-- proxy | Specification for on ...

  7. angularjs中安卓原生APP调用H5页面js函数,js写法应注意

    安卓原生app调用js方法,js方法应写在html下的script标签内,不能有任何function包裹,例如angular的controller层,这样APP也是获取不到的: 所以只有放在html中 ...

  8. ArcGIS案例学习笔记4_2_城乡规划容积率计算和建筑景观三维动画

    ArcGIS案例学习笔记4_2_城乡规划容积率计算和建筑景观三维动画 概述 计划时间:第4天下午 目的:城市规划容积率计算和建筑三维景观动画 教程: pdf page578 数据:实验数据\Chp13 ...

  9. K-means聚类算法(转)

    K-means聚类算法 想想常见的分类算法有决策树.Logistic回归.SVM.贝叶斯等.分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别与之对应.但是 ...

  10. 构建缓存gradle

    结合Kotlin使用Gradle build cache 宛丘之上兮 关注 2018.03.11 00:21* 字数 1177 阅读 505评论 5喜欢 4 在2017年4月,Gradle发布了bui ...