图文浅析之Android显示原理

一,写在前面

本篇文章会以图文的方式介绍Android设备的显示原理,不会深入到源码去分析一些细节,阅读本篇文章会对显示原理有个感性的认识,以便更好的理解Android性能优化相关的原理。


二,为什么要学习Android显示原理

大家玩手机上的应用,会经常遇到卡顿的情况。作为应用层的开发者,就需要在应用层尽可能的避免卡顿的发生,提供给用户一个良好的用户体验。一个经常卡顿的App是灾难性的,它会流失一批批忠实的粉丝。在这方面,个人认为微信就是一款非常nice的应用,这款强大的社交软件在经受上亿用户的锤炼下,并没有出现太多卡顿的情况。总体来说,体验很腾讯,用起来很流畅。回到正题,谈到“卡顿”,想必很多开发者并不真正理解what is 卡顿。

那么,什么是卡顿呢?卡顿的本质是出现掉帧。先了解一个概念,FPS,相信玩游戏的哥们对这个并不陌生,它表示每秒传递的帧数。操作应用时,不管是界面内容变化,还是动画的效果等等,都是由一帧一帧的画面组成。

通常Android显示设备的刷新率是60HZ,意味着每一帧要显示出来需要16ms(1000/60)。只要想办法在16ms内完成预计的一帧的画面处理,那么就不会出现卡顿。如果第1帧的画面超过16ms,那么在下一个16ms的周期到来时,由于第1帧的准备工作还没有完成,界面会继续显示第0帧的画面,这个时候就出现了掉帧,而掉帧就会导致卡顿。

如果不理解掉帧为啥导致卡顿,想象一种极限的情况:如果一帧画面的准备工作耗时1小时完成,那么界面上1h内只显示了一帧的画面,是不是就卡得忒夸张了点~

上面的介绍可知,卡顿就是显示出现了问题,因此需要了解Android的显示原理。了解一帧帧画面的数据是哪来的,处理数据的准备工作是怎么完成的,谁在调控16ms显示一帧画面等等相关知识点......


三,Android显示原理

Android显示原理的架构是C/S架构,S是系统层的服务SurfaceFlinger来扮演,它由C++语言编写;C一部分是Java提供给应用层使用的API,一部分是C++编写的底层实现。本篇文章不会从源码角度分析显示原理,有兴趣的哥们可以自行了解。了解Android的显示原理,需要从绘制原理刷新机制进行分析,下面会具体介绍,关于系统层的服务SurfaceFlinger将不再详细阐述。

Android的显示的大致流程:应用层将测量,布局,绘制后的数据给到SurfaceFlinger,SurfaceFlinger会将数据渲染到屏幕上,并配合Android的刷新机制来不断的刷新数据,最终显示出一帧帧的画面。

那么应用层绘制的数据,如何传递到系统层服务SurfaceFlinger的呢?

不同进程之间的通信,采用的肯定是跨进程的通信传输数据。Android采用的是一个叫SharedClient的匿名共享内存,来实现进程间数据的传输,它的底层实现仍是Binder机制。由于不同进程间是直接操作内存,因此匿名共享内存比一般的进程间通信的效率要高。若对匿名共享内存感兴趣,可参考文章匿名共享内存。

Android显示的框架图如下:

图文浅析之Android显示原理_第1张图片

一个应用对应一个SharedClient,一个SharedClient包含31个SharedBufferStack,每个SharedBufferStack包含两个缓存区(4.1版本以前)或三个缓存区(4.1版本以后)。每个SharedBufferStack对应一个Window,因此一个应用最多包含31个窗口。关于缓冲区,后面在介绍刷新机制时会介绍它。


四,绘制原理

我们知道View的绘制流程分为三个过程:Measure,Layout,Draw。

一个布局文件的View的层级图,如下所示:

图文浅析之Android显示原理_第2张图片

Measure

测量某一个View时,会先测量它的子View的宽高,然后再测量出自己的宽高大小,详情可参考文章View的测量原理。

Layout

设置某一个View的布局时,先确定其相对于父控件左上角的位置,再结合测量过程中得到的宽高数据,最终确定该View在应用程序窗口中的位置。

Draw

Android支持两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在3.0版本开始全面支持。

硬件加速的优点:在UI的显示和绘制上效率远高于CPU绘制;

硬件加速的缺点:

  1. GPU的功耗比CPU高
  2. 消耗内存更多
  3. 某些API不支持硬件加速

关于View绘制的三大流程的更多介绍,由于不是本篇文章的重点便不具体介绍啦,需要了解的童鞋可以查阅相关资料学习。


五,刷新机制

·通过上面的介绍,Android的显示流程大致分为三个部分:

  1. 应用层完成测量,布局,绘制的过程
  2. 将应用层绘制的数据,存放在匿名共享内存SharedClient中
  3. SurfaceFlinger将缓存中的数据渲染到屏幕上

那么SurfaceFlinger是如何将缓存中的数据,渲染到屏幕上的呢?

首先是CPU准备绘制过程中的数据,数据通过driver交给GPU渲染到屏幕上。这个driver是一个图形驱动,里面维护着一个队列。CPU将显示相关的数据放到队列里,GPU再将队列里的数据取出来,最后在屏幕上进行显示。

前面提到,Android的显示流程需要配合刷新机制,将一帧帧的画面显示在屏幕上。我们知道Android显示设备的FPS是60HZ,如果希望不发生卡顿,那么CPU/GPU需要在16ms内完成1帧的绘制。可以确定的是16ms就是一个周期,在该周期内要完成屏幕要求的一帧的绘制。CPU/GPU每16ms就绘制一帧,它为什么能遵循这个规定了呢


下面咱们先了解几个概念,分别是:双缓冲,三缓冲,VSYNC

在Android4.1之前,Android手机的流畅性方面是比较差的,于是Android团队在Android4.1版本推出了Project Butter。Project Butter在原有显示系统的基础上,添加了三个核心元素:VSYNC,Triple Buffer,Choreographer。

双缓冲:一个是Back Buffer,一个是Front Buffer,两个缓冲均在SharedBufferStack中。当一个Buffer中准备好数据后,通过io_ctrl来通知显示设备切换Buffer。

VSYNC:定时中断。双缓存需要CPU主动查询Buffer中数据是否准备好,才会去刷新屏幕,因此效率比较低。显示系统引入ASYNC之后,只要CPU收到ASYNC信号,就会开始处理一帧帧的数据。

三缓冲:即Triple Buffer。利用CPU/GPU的空闲时间准备数据,用于弥补在VSYNC+双缓冲配合使用的缺陷(下面会具体介绍)

另外,Choreographer起调度作用,在vsync信号到来时,使应用的绘制工作有序进行。


下面以时序图来分析Android显示系统的刷新机制,分为如下四种情况讨论。

1,没有VSYNC信号时

刷新机制的时序图如下:

图文浅析之Android显示原理_第3张图片

横坐标表示时间,一个周期16ms为一个格子。纵坐标分别是:CPU,GPU,显示设备。

第1个16ms时间内,CPU,GPU完成了第1帧处理;

第2个16ms时,CPU刚开始并未处理数据,而是在时间快结束时开始处理数据。该时间段内显示第1帧的数据,显示正常;

第3个16ms时,GPU仍在处理第2帧的数据。该时间段内显示第1帧的数据,显示不正常(本该显示第2帧);

我们发现,在没有VSYNC的情况下,CPU不知道什么时候开始处理数据,会出现两帧的时间,显示的一帧的画面。掉帧就会出现一定程度的卡顿,因此在Android4.1版本以后,引入了VSYNC信号来通知CPU处理数据。


2,引入VSYNC信号后

刷新机制的时序图如下:

图文浅析之Android显示原理_第4张图片

可以看到,每当VSYNC信号来临时,就会通知CPU开始处理数据。每一帧的数据在16ms内处理完成,就不会出现卡顿现象,当然这是一种理想情况。


3,引入VSYNC信号,某一帧数据在16ms内未完成处理

刷新机制的时序图如下:

图文浅析之Android显示原理_第5张图片

第1个16ms时间段里,A Butter由Display使用,CPU处理完了数据,B Buffer给GPU在使用,但GPU并未处理完数据(A,B Buffer就是双缓存的两个缓冲区);

第2个16ms时间段里,当VSYNC信号到来时,由于GPU需要处理前一帧的数据,CPU不再开始处理数据。等GPU处理完第1帧的数据后,CPU由于没有收到VSYNC信号,并不再处理第2帧的数据,等待下一个VSYNC信号的来临。此时,B Buffer由GPU使用,A Buffer由Display使用。屏幕仍显示第1帧时的画面,且cpu处于空闲状态。

那么,如何解决第2个16ms时间段里,当VSYNC信号到来时CPU及时的处理数据呢?答案是:使用Triple Buffer增加一个缓冲区,这就是显示系统新增的三缓冲技术。


4,引入VSYNC信号时,某一帧数据在16ms内未完成处理,使用三缓冲技术

刷新机制的时序图如下:

图文浅析之Android显示原理_第6张图片

在第2个16ms的时间段内,尽管GPU仍在处理前一帧的数据,当VSYNC信号到来时CPU就开始处理数据了。在第4个16ms的时间段里显示了C,比正常情况只延迟了16ms。三缓冲相对双缓冲降低了一定的延迟,保持了界面的流畅度。

注意:如果双缓冲可以正常的处理每帧数据,一般不会用到三缓冲。


六,卡顿的原因

卡顿的原因有两种:

  1. 主线程被阻塞,或者说主线程忙于处理其他操作。尽管Android系统的VSYNC信号每16ms就发送一次,但如果在16ms内CPU在做其他事情,并没有去准备UI显示相关的数据。那么依然会出现掉帧,掉帧就会导致卡顿。
  2. 绘制任务太重,绘制一帧的画面耗时高于16ms。本篇文章在多个地方提到:可能在某一帧的时间里,CPU/GPU并未处理完一帧的数据。于是出现掉帧,掉帧就会卡顿。

解决方案

第1种情况:避免在主线程执行耗时操作,以及和UI无关的操作;

第2中情况:优化布局,以及避免过度绘制;

 

                                                                                                                                    O(∩_∩)O

 

你可能感兴趣的:(App性能优化)