Flutter 中通过如下方式监听帧率,addTimingsCallback 涉及到帧调度知识,感兴趣可以看看这篇Flutter 帧调度过程。
这里重点说说 List<FrameTiming>。
addTimingsCallback 定义:
List<FrameTiming>可简单理解成:引擎层到框架层的帧数据流。
List<FrameTiming>则表示一系列实时帧信息。
如点击屏幕按钮,引擎将传递系列帧信息到框架层:“框架层,屏幕发送了变化,准备回调数据更新了!”。如果用户未操作,addTimesCallback 则不会回调。
因此 ,addTimesCallback(List<FrameTiming>)只有用户操作界面时参数才有值。
List<FrameTiming>中 0 的位置是第一帧,last 是最新一帧。 最新的帧永远在最后面。
通过这个单词不难猜测 Frame 表示帧,加上 Timing 可以理解成实时变化的帧。FrameTiming 是一个用来存储实时帧信息的数据结构。
FrameTiming 定义:
这里列了下我认为最重要的几个属性:
理解上述属性前需了解渲染相关知识,不清楚的可以看看Vsync 机制 和 卡顿产生原因 。
核心思想 图像内容展示到屏幕的过程需要 CPU 和 GPU 共同参与。CPU 负责计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。之后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。
当在应用中操作时候,就会产生连续的帧,如图:
每两个柱形一起表示一帧:ui 表示 cpu 耗时,raster 表示 gpu 耗时。
每帧细化后如下图,其中标注 ①②③④ 对应 FrameTiming 中的四个主要属性。而其中:
同时可推导出 FrameTiming 中相关衍生变量与上述重点关注属性关系:
④-① = totalSpan:同步信号开始到栅格化时间
②-① = vsyncOverhead:同步信号接受后到 ui 构建之间延迟。
③-② = buildDuration:ui 构建过程总时间。
④-③ = rasterDuration:栅格化过程总时间。
通过代码验证 Flutter 调试工具 PerformanceOverlay 中 Timing 每帧 ui 值和 ration 值与 vsyncstart、buildstart、buildFinish、rasterStart、rasterFinish 关系。
输出:
代码中,11 行是 ui 构建 + 栅格化时间,17 行是 totalSpan 时间, 22 行中是 vsyncOverhead + ui 构建 + 栅格化时间 这个值最终和才等于 totalSpan 值。
这里有个误区, 网上很少人关注 totalSpan 与 buildDuration+rasterDuration 关系,好像默认就是相等的。其实,totalSpan 不等于 Timing 中 ui + raster 值,而是 Vsync 信号接受后构建之前延迟 vsyncOverhead+cpu 构建耗时 + gpu 耗时,
通过上述案例和 totalSpan 定义很容易佐证这点:
核心思路
代码如下:
这里拆解下其中逻辑,方便理解。
有 5 帧,其中在实际绘制过程中 f① 和 f② 都是在正常时间范围内绘制,f③ 则会绘制耗时,跨越 2 帧。
假设 f①,f②,f③ 绘制总耗时为 P1, P2, P3 则:
上面代码在刷新率为 60HZ 的手机上每秒绘制帧时间为 16.6 是没有问题的,但是如果在其他帧率的手机上,比如 90HZ(OnePlus 7 Pro), 120HZ(Redmi K30)上就会存在问题。
思路:通过通道获取各系统提供的刷新率获取方式,然后更新上述代码中的刷新率。
在 Android 和 ios 平台提供了获取帧率的方法。
定义接口
本文重点讲解了 FrameTiming 结构在帧显示过程中的对应关系,图解获取准确帧的算法,最后完善了获取帧的逻辑。
总体来说网上能搜到的我这里都有,在学习过程中遇到 FrameTiming 结构和帧率计算方法这两个点觉得不好理解,不够系统,就重点介绍争取深入浅出表达出来。不足之处还望各位大佬指出,谢谢!