【总结】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 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Kubernetes常用命令大全近期总结

《Kubernetes常用命令大全近期总结》Kubernetes是用于大规模部署和管理这些容器的开源软件-在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用Kubernetes(有时被称为“... 目录前言Kubernetes 的工作原理为什么要使用 Kubernetes?Kubernetes常用命令总

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Python中实现进度条的多种方法总结

《Python中实现进度条的多种方法总结》在Python编程中,进度条是一个非常有用的功能,它能让用户直观地了解任务的进度,提升用户体验,本文将介绍几种在Python中实现进度条的常用方法,并通过代码... 目录一、简单的打印方式二、使用tqdm库三、使用alive-progress库四、使用progres

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二