图文浅析之Android显示原理

作者: Kepler_II | 来源:发表于2021-05-10 20:50 被阅读0次

    一、写在前面

    本篇文章会以图文的方式介绍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显示的框架图如下:

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

    四、绘制原理

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

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

    Measure

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

    Layout

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

    Draw

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

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

    硬件加速的缺点:

    GPU的功耗比CPU高
    消耗内存更多
    某些API不支持硬件加速
    关于View绘制的三大流程的更多介绍,由于不是本篇文章的重点便不具体介绍啦,需要了解的童鞋可以查阅相关资料学习。

    五、刷新机制

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

    应用层完成测量,布局,绘制的过程
    将应用层绘制的数据,存放在匿名共享内存SharedClient中
    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信号时

    刷新机制的时序图如下:


    横坐标表示时间,一个周期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信号后

    刷新机制的时序图如下:


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

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

    刷新机制的时序图如下:

    第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内未完成处理,使用三缓冲技术

    刷新机制的时序图如下:


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

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

    六、卡顿的原因

    卡顿的原因有两种:

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

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

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

    本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

    相关文章

      网友评论

        本文标题:图文浅析之Android显示原理

        本文链接:https://www.haomeiwen.com/subject/dofwdltx.html