Android 显示刷新机制、VSYNC和三重缓存机制

2024-06-12 10:18

本文主要是介绍Android 显示刷新机制、VSYNC和三重缓存机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android 显示刷新机制、VSYNC和三重缓存机制


为了理解 APP 是如何进行渲染的,我们就必须了解手机硬件是如何工作的,也必须理解什么是 VSYNC。

首先,我们需要了解2个相关概念:

  • 刷新率(Refresh Rate):代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如 60Hz。
  • 帧率(Frame Rate):代表了 GPU 在一秒内绘制操作的帧数,例如 30fps,60fps。

GPU 会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。

在这里插入图片描述

如果刷新率和帧率,各自做自己的事,不相互协调工作,那么刷新频率和帧率并不总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现画面撕裂(Tearing)的现象,也就是画面上下两部分显示内容发生断裂,来自不同的两帧数据发生重叠。

在这里插入图片描述
在这里插入图片描述

为了解决 Tearing 问题,Android 引入了 VSYNC 信号以及双重与三重缓存机制。

Android 黄油计划

从 Android 4.1 开始,谷歌致力于解决 Android 系统中最饱受诟病的一个问题,滑动不如 iOS 流畅。因谷歌在 4.1 版本引入了一个重大的改进—Project Butter,也即是黄油计划。

Project Butter 对 Android Display 系统进行了重构,引入了三个核心元素,即 VSYNC、Triple Buffer 和 Choreographer。Choreographer 在之前的文章《从源码分析Choreographer是如何实现VSYNC信号的请求及帧的刷新处理?(Android Q)》中已经分析过了,三重缓存机制我们后面介绍,这里我们重点讲解 VSYNC 的作用。

VSYNC(Vertical Synchronization)是一个相当古老的概念,对于游戏玩家,它有一个更加大名鼎鼎的中文名字—-垂直同步。垂直同步(vsync)指的是显卡的输出帧数和屏幕的垂直刷新率相同。在当下,垂直同步的含义我们可以理解为,使得显卡生成帧的速度和屏幕刷新的速度的保持一致。举例来说,如果屏幕的刷新率为 60Hz,那么生成帧的速度就应该被固定在 16ms。

上文中,我们已经知道了什么事画面撕裂(Tearing)现象以及它产生的原因,而 VSYNC 最重要的作用是防止出现画面撕裂。

VSYNC 信号是由屏幕(显示设备)产生的,并且以 60fps 的固定频率发送给 Android 系统,Android 系统中的 SurfaceFlinger 接收发送的 VSYNC 信号。VSYNC 信号表明可对屏幕进行刷新而不会产生撕裂。当 SurfaceFlinger 接收到 VSYNC 信号后,SurfaceFlinger 会遍历其层列表,以查找新的缓冲区。如果 SurfaceFlinger 找到新的缓冲区,SurfaceFlinger 会获取缓冲区;否则,SurfaceFlinger 会继续使用上一次获取的那个缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。

通常来说,帧率超过刷新频率只是一种理想的状况,在超过 60fps 的情况下,GPU 所产生的帧数据会因为等待 VSYNC 的刷新信息而被 Hold 住,这样能够保持每次刷新都有实际的新的数据可以显示。但是我们遇到更多的情况是帧率小于刷新频率。

在这里插入图片描述

在这种情况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过 60fps 突然掉到 60fps 以下,这样就会发生 LAG,JANK,HITCHING 等卡顿掉帧的不顺滑的情况。这也是用户感受不好的原因所在。

接下来,我们以具体示例来看 VSYNC 的作用。

没有使用 VSYNC 时

我们来看没有 VSYNC 的情况:

在这里插入图片描述

这个图中有三个元素,Display 是显示屏幕,GPU 和 CPU 负责渲染帧数据,每个帧以方框表示,并以数字进行编号,如0、1、2等等。

  1. CPU 正常执行帧1,GPU 正常渲染帧1,所以帧1正常显示。
  2. 但,CPU 由于被占用等原因,等到即将显示帧2时,它才开始处理第二帧的内容,这显然完不成了,所以等到第二帧显示的时候,只能使用上一帧的内容显示了,也即是丢帧了。

上面丢帧的原因,我们可以从图中看出,是因为新的一帧开始的时候,CPU 在处理其他任务,并没有马上执行下一帧的任务,那么如何让 CPU 在新的一帧开始的时候立即处理显示内容呢?答案就在 VSYNC 身上!

使用 VSYNC 信号

我们来看,Android 引入 VSYNC 之后的帧执行示意图:

在这里插入图片描述

  1. 第0帧显示时,CPU 和 GPU 准备好了第一帧的内容。
  2. 第1帧刚开始显示时,CPU 放下手中的任务,立马处理第2帧显示相关的任务(这里使用了消息屏障机制,可以参考前文《Android消息循环的同步屏障机制及UI渲染性能的提升(Android Q)》),这样,在第二帧显示之前, CPU 和 GPU 也提前完成了显示任务的处理,第二帧正常显示。

可以看到,使用 VSYNC 信号机制,提升了渲染任务的优先级,优化了渲染性能,可有效的减少了丢帧、卡顿等问题。

但是上图中仍然存在一个问题:CPU 和 GPU 处理数据的速度似乎都能在 16ms 内完成,而且还有时间空余,也就是说,CPU 和 GPU 的帧率要高于 Display 的帧率。由于 CPU/GPU 只在收到 VSYNC 时才开始数据处理,故它们的帧率被拉低到与 Display 相同。但这种处理并没有什么问题,因为 Android 设备的 Display FPS 一般是 60,其对应的显示效果非常平滑。

但如果 CPU/GPU 的帧率小于 Display 的帧率,情况又不同了,将会发生如下图的情况:

在这里插入图片描述

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

以上是使用双重缓存机制时产生的问题,那么又如何来解决呢?

为了解决这个问题,Android 引入了 Triple Buffer 机制。

三重缓存机制(Triple Buffer)

一般我们在绘制 UI 的时候,都会采用一种称为“双缓存”的技术(例如,上面几个例子)。双缓存意味着要使用两个缓存区,其中一个称为 Front Buffer,另外一个称为 Back Buffer。UI 总是先在 Back Buffer 中绘制,然后再和 Front Buffer 交换,渲染到显示设备中。理想情况下,这样一个刷新会在 16ms 内完成,下图就是描述的这样一个刷新过程:Display 处理前 Front Buffer,CPU、GPU 处理 Back Buffer。

只有两个 Buffer(Android 4.1之前)时,CPU 在空闲时,如果 Back Buffer 被占用了,它也只能等待 GPU 使用之后再次进行写入。我们可以想想,如果有第三个 Buffer 的存在,CPU 是不是就可以提前工作,而不至于空闲了?所以,Google 在 Android4.1 以后,引入了三重缓存机制:Tripple Buffer。Tripple Buffer 利用 CPU/GPU 的空闲等待时间提前准备好数据,并不一定会使用。

引入 Triple Buffer 效果如下图所示:

在这里插入图片描述

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

那么,是不是 Buffer 越多越好呢?回答是否定的。由上图可知,在第二个时间段内,CPU 绘制的第 C 帧数据要到第四个 16ms 才能显示,这比双 Buffer 情况多了 16ms 延迟,并且大量的缓存数据也会导致内存增大,以及显示数据是否失效等问题。所以,Buffer 三个足矣。

总结


  1. 刷新率(Refresh Rate):代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如 60Hz。

  2. 帧率(Frame Rate):代表了 GPU 在一秒内绘制操作的帧数,例如 30fps,60fps。

  3. GPU 会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。

  4. 如果刷新率和帧率,各自做自己的事,不相互协调工作,那么刷新频率和帧率并不总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现画面撕裂(Tearing)的现象。

  5. 从 Android 4.1 开始,谷歌在黄油计划中,引入了了三个核心元素,即 VSYNC、Triple Buffer 和 Choreographer。

  6. VSYNC 信号是由屏幕(显示设备)产生的,并且以 60fps 的固定频率发送给 Android 系统,Android 系统中的 SurfaceFlinger 接收发送的 VSYNC 信号。VSYNC 信号表明可对屏幕进行刷新而不会产生撕裂。

  7. 使用 VSYNC 信号机制,提升了渲染任务的优先级,优化了渲染性能,可有效的减少了丢帧、卡顿等问题。

  8. 三重缓存机制(Triple Buffer) 利用 CPU/GPU 的空闲等待时间提前准备好数据,有效的提升了渲染性能。


**PS:更多精彩内容,请查看 --> 《Android 性能优化》
**PS:更多精彩内容,请查看 --> 《Android 性能优化》
**PS:更多精彩内容,请查看 --> 《Android 性能优化》

这篇关于Android 显示刷新机制、VSYNC和三重缓存机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

ABAP怎么把传入的参数刷新到内表里面呢?

1.在执行相关的功能操作之前,优先执行这一段代码,把输入的数据更新入内表里面 DATA: lo_guid TYPE REF TO cl_gui_alv_grid.CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'IMPORTINGe_grid = lo_guid.CALL METHOD lo_guid->check_changed_data.CALL M

据阿谱尔APO Research调研显示,2023年全球髓内钉市场销售额约为4.7亿美元

根据阿谱尔 (APO Research)的统计及预测,2023年全球髓内钉市场销售额约为4.7亿美元,预计在2024-2030年预测期内将以超过3.82%的CAGR(年复合增长率)增长。 髓内钉市场是指涉及髓内钉制造、分销和销售的行业。髓内钉是一种用于整形外科手术的医疗器械,用于稳定长骨骨折,特别是股骨、胫骨和肱骨。髓内钉通常由不銹钢或钛等材料制成,并插入骨的髓管中,以在愈合过程中提供结构支

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.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

vue+elementUI下拉框联动显示

<el-row><el-col :span="12"><el-form-item label="主账号:" prop="partyAccountId" :rules="[ { required: true, message: '主账号不能为空'}]"><el-select v-model="detailForm.partyAccountId" filterable placeholder="