【总结】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

相关文章

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Java反转字符串的五种方法总结

《Java反转字符串的五种方法总结》:本文主要介绍五种在Java中反转字符串的方法,包括使用StringBuilder的reverse()方法、字符数组、自定义StringBuilder方法、直接... 目录前言方法一:使用StringBuilder的reverse()方法方法二:使用字符数组方法三:使用自

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D