Flutter帧率监控 | 由浅入深,详解获取帧率的那些事
前言
做线上帧率监控上报时,少不了需要弄明白如何通过代码获取实时帧率的需求,这篇文章通过图解配合Flutter性能调试工具的方式一步步通俗易懂地让你明白获取帧率的基础知识,以后再也不愁看不懂调试工具上指标了。
说说 List<FrameTiming>
Flutter 中通过如下方式监听帧率,addTimingsCallback 涉及到帧调度知识,感兴趣可以看看这篇Flutter 帧调度过程。

这里重点说说 List<FrameTiming>。
List<FrameTiming>从哪里来
addTimingsCallback 定义:

List<FrameTiming>可简单理解成:引擎层到框架层的帧数据流。
List<FrameTiming>何时有值
List<FrameTiming>则表示一系列实时帧信息。
如点击屏幕按钮,引擎将传递系列帧信息到框架层:“框架层,屏幕发送了变化,准备回调数据更新了!”。如果用户未操作,addTimesCallback 则不会回调。
因此 ,addTimesCallback(List<FrameTiming>)只有用户操作界面时参数才有值。
List<FrameTiming>中帧存储顺序
List<FrameTiming>中 0 的位置是第一帧,last 是最新一帧。 最新的帧永远在最后面。
再说说 FrameTiming
通过这个单词不难猜测 Frame 表示帧,加上 Timing 可以理解成实时变化的帧。FrameTiming 是一个用来存储实时帧信息的数据结构。
FrameTiming 定义:

这里列了下我认为最重要的几个属性:
前置知识简单说明
理解上述属性前需了解渲染相关知识,不清楚的可以看看Vsync 机制 和 卡顿产生原因 。
核心思想
图像内容展示到屏幕的过程需要 CPU 和 GPU 共同参与。CPU 负责计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。之后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。
FrameTiming 在帧中的表示
当在应用中操作时候,就会产生连续的帧,如图:

每两个柱形一起表示一帧:ui 表示 cpu 耗时,raster 表示 gpu 耗时。
每帧细化后如下图,其中标注 ①②③④ 对应 FrameTiming 中的四个主要属性。而其中:
- ui 在 FrameTiming 中有对应衍生变量叫 buildDuration 。
- Raster 在 FrameTiming 中用 RasterDuration 表示。

同时可推导出 FrameTiming 中相关衍生变量与上述重点关注属性关系:
④-① = totalSpan:同步信号开始到栅格化时间
②-① = vsyncOverhead:同步信号接受后到 ui 构建之间延迟。
③-② = buildDuration:ui 构建过程总时间。
④-③ = rasterDuration:栅格化过程总时间。
totalSpan 与 buildDuration+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 定义很容易佐证这点:

如何获取帧率
核心思路
- 将原始帧数据 List降噪保留最新关注帧数。
- 通过公式 FPS≈ REFRESH_RATE * 实际绘制帧数 / 理论绘制帧数 。
如何降噪
从原生数据中筛查最新关注帧数,其他都干掉。
如下,通过栈方式调换了存储方式更容易操作,然后将栈中老的干掉只保留最新的关注 100 条。

将位于不同帧的无效数据过滤掉。
如下,以刷新率为 60 举例,如果一帧之间的时间 > 16.6 *2,该帧就位于不同帧中,因为一帧最大时间也就是 16.6ms。

如何计算
代码如下:

这里拆解下其中逻辑,方便理解。
有 5 帧,其中在实际绘制过程中 f① 和 f② 都是在正常时间范围内绘制,f③ 则会绘制耗时,跨越 2 帧。

假设 f①,f②,f③ 绘制总耗时为 P1, P2, P3 则:
理论绘制帧数 = (P1 / 16.6)+ 1 + (P2 / 16.6) + 1 + (P3 / 16.6) + 1 图中明显可以看到 P1 和 P2 < 16.6, 而 P3 > 16.6 *2 ,所有理论绘制帧数 = 0 +1 + 0 + 1 + 2 + 1 = 5。
实际绘制帧数 = 3 。
本来正常应该绘制 5 帧,但是实际绘制 3 帧,取比值表示实际绘制能力,根据 FPS≈ REFRESH*RATE * 实际绘制帧数 / 理论绘制帧数 。 即 FPS = 3 _ 60 / 5。
完整代码

效果展示

这就结束了?
上面代码在刷新率为 60HZ 的手机上每秒绘制帧时间为 16.6 是没有问题的,但是如果在其他帧率的手机上,比如 90HZ(OnePlus 7 Pro), 120HZ(Redmi K30)上就会存在问题。
- 代码中写死了 REFRESH_RATE = 60 。
- maxframes = 100 也有问题,如果在 60HZ 手机上取 100 帧绰绰有余,在 120HZ 手机上的话,每秒绘制 120 帧显然不够。
如何获取帧率(改进版)
思路:通过通道获取各系统提供的刷新率获取方式,然后更新上述代码中的刷新率。
获取各系统帧率
在 Android 和 ios 平台提供了获取帧率的方法。
- 对于 Android 通过 WindowManager 获取刷新率:

- 对于 iOS 从 CADisplayLink获取刷新率:

定义统一获取接口并实现(以安卓为例)

定义接口

最终修改点
- 最大帧率数修改成 120。
- fpsHZ 这个值通过插件动态获取。
- 时间间隔也同步修改下,也就是 16.6(60hz 的时候)。
- 最后 fps 计算公式中的刷新率同步修改成 fpsHZ。

总结
本文重点讲解了 FrameTiming 结构在帧显示过程中的对应关系,图解获取准确帧的算法,最后完善了获取帧的逻辑。
总体来说网上能搜到的我这里都有,在学习过程中遇到 FrameTiming 结构和帧率计算方法这两个点觉得不好理解,不够系统,就重点介绍争取深入浅出表达出来。不足之处还望各位大佬指出,谢谢!
如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。
️ 本文原创听蝉 公众号:码里特别有禅 欢迎关注原创技术文章第一时间推送 ️
PS: 文中所有源码获取方式:公众号后台回复 “fps”
参考链接
如何代码获取 Flutter APP 的 FPS - Yrom's
allenymt/flutter_fps: flutter Fps 的两种监听方案
如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。
️ 本文原创听蝉 公众号:码里特别有禅 欢迎关注原创技术文章第一时间推送 ️
Flutter帧率监控 | 由浅入深,详解获取帧率的那些事的更多相关文章
- Spring Boot Actuator监控使用详解
在企业级应用中,学习了如何进行SpringBoot应用的功能开发,以及如何写单元测试.集成测试等还是不够的.在实际的软件开发中还需要:应用程序的监控和管理.SpringBoot的Actuator模块实 ...
- Flutter完整开发实战详解
Flutter完整开发实战详解(一.Dart语言和Flutter基础) Flutter完整开发实战详解(二. 快速开发实战篇) Flutter完整开发实战详解(三. 打包与填坑篇)
- Linux 系统性能监控命令详解
Linux 系统性能监控命令详解 CPU MEMORY IO NETWORK LINUX进程内存占用查看方法 系统负载过重时往往会引起其它子系统的问题,比如:->大量的读入内存的IO请求(pag ...
- Zabbix 监控过程详解
Zabbix 监控过程详解 一.修改密码及中文语言 修改密码 修改中文语言 如果复选框中没有 Chinese(zh_CN) 选项,需要安装中文包 [root@Zabbix-server ~]# yum ...
- 搭建zabbix监控系统详解
搭建zabbix监控系统详解 文:warren 博文大纲:一.前言 二.zabbix监控架构三.搭建Zabbix监控服务器四.搭建过程中遇到有些服务无法正常启动的解决办法 一.前言 : 要想实时的 ...
- 《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(5)-Fiddler监控面板详解
1.简介 按照从上往下,从左往右的计划,今天就轮到介绍和分享Fiddler的监控面板了.监控面板主要是一些辅助标签工具栏.有了这些就会让你的会话请求和响应时刻处监控中毫无隐私可言.监控面板是fiddl ...
- JMeter性能测试-服务器资源监控插件详解
零.引言 我们对被测应用进行性能测试时,除了关注吞吐量.响应时间等应用自身的表现外,对应用运行所涉及的服务器资源的使用情况,也是非常重要的方面,通过实时监控,可以准确的把握不同测试场景下服 ...
- (转)JMeter性能测试-服务器资源监控插件详解
零.引言 我们对被测应用进行性能测试时,除了关注吞吐量.响应时间等应用自身的表现外,对应用运行所涉及的服务器资源的使用情况,也是非常重要的方面,通过实时监控,可以准确的把握不同测试场景下服务器资源消耗 ...
- JVM监控命令详解(转)
JVM监控命令基本就是 jps.jstack.jmap.jhat.jstat 几个命令的使用就可以了 JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外 ...
- Wordpress 网站搭建及性能监控方法详解!
前言 说到 Wordpress,大家往往想到的是博客,其实,如今的 WordPress 已经成为全球使用量最多的开源 CMS 系统.并且,如果你有一定的技术基础稍加改动,就可以搭建出新闻网站.企业网站 ...
随机推荐
- 齐博x1标签实例:做模板组图单图无图混排的处理
代码如下, {qb:tag name="xxx" type="cms" rows="10"} {if ( count($rs['picurl ...
- 十一、Pod的健康检查-探针
Pod 的健康检查-探针 一.Pod 的健康检查-探针 1.1.探针基本概念 探针是由 kubelet 对容器执行的定期诊断.要执行诊断,kubelet 调用由容器实现的 Handler 有三种类型 ...
- 常用Linux命令(常年更新)
Linux后台运行脚本: nohup python -u test.py > out.log 2>&1 & nohup sh **.sh > /dev/null 2& ...
- CSS布局秘籍(1)-任督二脉BFC/IFC
01.CSS布局 1.1.正常布局流(Normal flow) 正常布局流 就是不做任何布局控制,按照HTML的顺序(从左到右,从上而下)进行布局排列.网页基于盒子模型进行正常的布局,主要特点: 盒子 ...
- android学习 基础知识
布局管理器 大小用dp; 字体用sp 线性布局 与 相对布局是可以嵌套使用的,根据实际需求,灵活使用. 1.通用属性 # 大小相关 layout_width="100dp":宽度 ...
- 解决vue中对象属性改变视图不更新的问题
在使用VUE的过程中,会遇到这样一种情况, vue data 中的数据更新后,视图没有自动更新. 这个情况一般分为两种, 一种是数组的值改变,在改变数组的值的是时候使用索引值去更改某一项,这样视图不会 ...
- 记一次windows10电脑连上wifi无法上网的解决问题
前言 今天下午同学的电脑能连上wifi,但是却上不了互联网 开始思考 首先想到的肯定是WiFi驱动问题,但是wifi能连上大部分的原因可能就不是驱动问题了,为了彻底排除这个因素,我用到了我的usb网卡 ...
- 关于mysql命令的学习
前言 这篇文章我不会具体说明哪些操作有哪些命令,我只说明这些命令的记忆方法.其实知道原理后,这些命令尽管多,但都是有据可循的 mysql提示符 可以通过登录时加入 –prompt 提示符内容 参数 登 ...
- VBA驱动SAP GUI自动化:查找页面元素FindAllByName
我们在VBA中嵌入SAP原生的[脚本录制与回放]功能生成的VBS脚本,可以实现很多自动化操作.但只有我们对SAP做了一系列动作,才会得到这些动作的脚本.然而,一旦我们需要用代码提前做一些判断,然后再决 ...
- SpringCloud Alibaba(三) - GateWay网关
1.基本环境搭建 1.1 依赖 <!-- Gatway 网关会和springMvc冲突,不能添加web依赖 --> <dependency> <groupId>or ...