【总结】Android的16ms和垂直同步以及三重缓存

2024-02-09 06:58

本文主要是介绍【总结】Android的16ms和垂直同步以及三重缓存,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

手机屏幕是由许多的像素点组成的,每个像素点通过显示不同的颜色最终屏幕呈现各种各样的图像。手机系统的类型和手机硬件的不同导致UI的流畅性体验个不一致。

屏幕展示的颜色数据

  • 在GPU中有一块缓冲区叫做 Frame Buffer ,这个帧缓冲区可以认为是存储像素值的二位数组。
  • 数组中的每一个值就对应了手机屏幕的像素点需要显示的颜色。
  • 由于这个帧缓冲区的数值是在不断变化的,所以只要完成对屏幕的刷新就可以显示不同的图像了.。
  • 至于刷新工作手记的逻辑电路会定期的刷新 Frame Buffer的 目前主流的刷新频率为60次/秒 折算出来就是16ms刷新一次。

GPU的Frame Buffer中的数据

  • GPU 除了帧缓冲区用以交给手机屏幕进行绘制外. 还有一个缓冲区 Back Buffer 这个用以交给应用的,让CPU往里面填充数据。
  • GPU会定期交换 Back Buffer 和 Frame Buffer ,也就是对Back Buffer中的数据进行栅格化后将其转到 Frame Buffer 然后交给屏幕进行显示绘制,同时让原先的Frame Buffer 变成 Back Buffer 让程序处理。

Android的16ms

在Android中我们一般都会提到16ms绘制一次,那么到底是那里控制这16ms的呢?

Choreographer类中我们有一个方法获取屏幕刷新速率:

public final class Choreographer {private static float getRefreshRate() {DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(Display.DEFAULT_DISPLAY);return di.refreshRate;}
}/*** Describes the characteristics of a particular logical display.* @hide*/
public final class DisplayInfo implements Parcelable {/*** The refresh rate of this display in frames per second.* <p>* The value of this field is indeterminate if the logical display is presented on* more than one physical display.* </p>*/public float refreshRate;
}final class VirtualDisplayAdapter extends DisplayAdapter {private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {@Overridepublic DisplayDeviceInfo getDisplayDeviceInfoLocked() {if (mInfo == null) {mInfo = new DisplayDeviceInfo();mInfo.name = mName;mInfo.uniqueId = getUniqueId();mInfo.width = mWidth;mInfo.height = mHeight;mInfo.refreshRate = 60;/***部分代码省略***/}return mInfo;}}
}

一秒60帧,计算下来大概16.7ms一帧。

屏幕绘制

作为严重影响 Android 口碑问题之一的UI流畅性差的问题,首先在 Android 4.1 版本中得到了有效处理。其解决方法就是本文要介绍的 Project Butter

Project ButterAndroid Display 系统进行了重构,引入了三个核心元素,即 VSYNCTriple BufferChoreographer 。其中, VSYNC 是理解 Project Buffer 的核心。VSYNCVertical Synchronization(垂直同步) 的缩写,是一种在 PC 上已经很早就广泛使用的技术。 可简单的把它认为是一种定时中断。

接下来,将围绕 VSYNC 来介绍 Android Display 系统的工作方式。请注意,后续讨论将以Display 为基准,将其划分成 16ms 长度的时间段, 在每一时间段中,Display 显示一帧数据(相当于每秒 60 帧)。时间段从 1 开始编号。

没有VSYNC的情况:

image

由上图可知

  1. 时间从 0 开始,进入第一个 16msDisplay 显示第 0 帧,CPU 处理完第一帧后,GPU 紧接其后处理继续第一帧。三者互不干扰,一切正常。
  2. 时间进入第二个 16ms:因为早在上一个16ms时间内,第1帧已经由CPUGPU处理完毕。故Display可以直接显示第1帧。显示没有问题。但在本16ms期间,CPUGPU 却并未及时去绘制第2帧数据(注意前面的空白区),而是在本周期快结束时,CPU/GPU才去处理第2帧数据。
  3. 时间进入第316ms,此时Display应该显示第2帧数据,但由于CPUGPU还没有处理完第2帧数据,故Display只能继续显示第一帧的数据,结果使得第1帧多画了一次(对应时间段上标注了一个Jank)。
  4. 通过上述分析可知,此处发生Jank的关键问题在于,为何第116ms段内,CPU/GPU没有及时处理第2``帧数据?原因很简单,CPU可能是在忙别的事情(比如某个应用通过sleep 固定时间来实现动画的逐帧显示),不知道该到处理 UI绘制 的时间了。可 CPU 一旦想起来要去处理第 2 帧数据,时间又错过了!

NSYNC的出现

为解决这个问题,Project Buffer引入了VSYNC,这类似于时钟中断。结果如图所示:

image

由图可知,每收到VSYNC中断,CPU就开始处理各帧数据。整个过程非常完美。

不过,仔细琢磨图2却会发现一个新问题:图2中,CPUGPU处理数据的速度似乎都能在16ms内完成,而且还有时间空余,也就是说,CPU/GPUFPS(帧率,Frames Per Second)要高于DisplayFPS

确实如此。由于CPU/GPU只在收到VSYNC时才开始数据处理,故它们的FPS被拉低到与DisplayFPS相同。但这种处理并没有什么问题,因为Android设备的Display FPS一般是60,其对应的显示效果非常平滑。 如果CPU/GPUFPS小于DisplayFPS,会是什么情况呢?请看下图:

image

由图可知:

  1. 在第二个16ms时间段,Display本应显示B帧,但却因为GPU还在处理B帧,导致A帧被重复显示。
  2. 同理,在第二个16ms时间段内,CPU无所事事,因为A BufferDisplay在使用。B BufferGPU在使用。注意,一旦过了VSYNC时间点, CPU就不能被触发以处理绘制工作了。

三级缓存

为什么CPU不能在第二个16ms处开始绘制工作呢?原因就是只有两个Buffer。如果有第三个Buffer的存在,CPU就能直接使用它, 而不至于空闲。出于这一思路就引出了Triple Buffer。结果如图所示:

image

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

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

以上对VSYNC进行了理论分析,其实也引出了Project Buffer的三个关键点: 核心关键:需要VSYNC定时中断。 Triple Buffer:当双Buffer不够使用时,该系统可分配第三块Buffer。 另外,还有一个非常隐秘的关键点:即将绘制工作都统一到VSYNC时间点上。这就是Choreographer的作用。在它的统一指挥下,应用的绘制工作都将变得井井有条。

转自MrlLeed的: Android垂直同步和三重缓存

如果有对源码有兴趣的话可以继续阅读另一篇文章:Android系统的编舞者Choreographer

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!

这篇关于【总结】Android的16ms和垂直同步以及三重缓存的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/693461

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

十五.各设计模式总结与对比

1.各设计模式总结与对比 1.1.课程目标 1、 简要分析GoF 23种设计模式和设计原则,做整体认知。 2、 剖析Spirng的编程思想,启发思维,为之后深入学习Spring做铺垫。 3、 了解各设计模式之间的关联,解决设计模式混淆的问题。 1.2.内容定位 1、 掌握设计模式的"道" ,而不只是"术" 2、 道可道非常道,滴水石穿非一日之功,做好长期修炼的准备。 3、 不要为了

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义,意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换,神经网络能够学习数据的特征和模式,实现对复杂数据的建模和预测。(我们可以借助人类的神经元模型来更好的帮助我们理解该算法的本质,不过这里需要说明的是,虽然名字是神经网络,并且结构等等也是借鉴了神经网络,但其原型以及算法本质上还和生物层面的神经网络运行原理存在

Java注解详细总结

什么是注解?         Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序。         注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。 自定义注解  现在我们自定义一个MyTest注解 public @interface MyTest{String aaa();boolean bbb()