前言在做在线帧率监控上报的时候,需要搞清楚如何通过代码获取实时帧率。本文通过图解配合Flutter性能调试工具的方式,让你一步步了解获取帧率的基础知识,以后再也不用担心看不懂调试工具上的指标了。说说ListFlutter通过以下方式监控帧率。addTimingsCallback涉及帧调度知识。有兴趣可以阅读这篇Flutter帧调度流程。这里我们重点关注List。List从哪里来addTimingsCallback定义:List可以简单理解为:帧数据从引擎层流向帧层。
List
有值时,List表示一系列实时帧信息。如果点击屏幕按钮,引擎将向框架层传递一系列帧信息:“框架层,屏幕已发送变化,回调数据更新准备就绪!”。如果用户没有操作,addTimesCallback不会被回调。所以addTimesCallback(List)的参数只有在用户操作界面时才有值。List中0的位置是第一帧,last是最新一帧。最新的帧总是最后的。再说FrameTiming,通过这个词不难猜到Frame是帧的意思,Timing可以理解为实时变化的帧。FrameTiming是一种用于存储实时帧信息的数据结构。FrameTiming定义:下面是我认为最重要的属性:-1.image"alt=""width="70%"/>前置知识简要说明在理解以上属性之前,需要了解渲染相关的知识。如果不确定,可以查看Vsync机制和卡顿原因。核心思想将图像内容显示到屏幕上的过程需要CPU和GPU的共同参与。CPU负责计算显示内容,如视图创建、布局计算、图片解码、文字绘制等,然后CPU将计算出的内容提交给GPU,由GPU进行变换、合成、渲染。之后,GPU会将渲染结果提交到帧缓冲区,等待下一个VSync信号显示在屏幕上。由于垂直同步机制,如果CPU或GPU在一个VSync时间内没有完成内容提交,则该帧将被丢弃,等待下一次机会再次显示,而显示器则保持之前的内容不变。FrameTiming在frame中的表示在应用程序中运行时会产生连续的帧,如图:每两个bar一起代表一个frame:ui代表cpu耗时,raster代表gpu耗时。每帧细化如下图所示,其中①②③④分别对应FrameTiming中的四个主要属性。其中:ui在FrameTiming中有一个对应的衍生变量叫buildDuration。Raster在FrameTiming中用RasterDuration表示。同时可以推导出FrameTiming中的相关派生变量与上述关键属性的关系:④-①=totalSpan:从同步信号开始到光栅化的时间②-①=vsyncOverhead:接收同步信号和构建用户界面之间的延迟。③-②=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*实际绘制帧数/理论绘制帧数。如何降低噪声从原始数据中筛选出感兴趣的最新帧,并去除其他所有内容。如下,通过stack的方式切换存储方式更容易操作,然后kill掉stack中旧的,只保留最新的100个attention。过滤掉位于不同帧中的无效数据。如下,以刷新率为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≈REFRESHRATE,实际绘制帧数/理论绘制帧数。即FPS=3\_60/5。完整的代码效果展示到此结束?以上代码在刷新率为60HZ的手机上在16.6每秒画帧没有问题,但是在其他帧率的手机上就会有问题,比如90HZ(一加7Pro)、120HZ(红米)K30)。REFRESH_RATE=60是硬编码的。maxframes=100还有一个问题,如果100帧在60HZ的手机上绰绰有余,那么在120HZ的手机上每秒画120帧显然是不够的。如何获取帧率(改进版):通过通道获取各个系统提供的刷新率获取方式,然后在上面的代码中更新刷新率。获取各系统帧率提供了获取Android和ios平台帧率的方法。对于Android,通过WindowManager获取刷新率:对于iOS,通过CADisplayLink获取刷新率:定义统一的获取接口,实现(以Android为例)定义接口的最终修改点最大帧率为120。fpsHZ这个值是插件动态获取的。时间间隔也同步修改,为16.6(at60hz)。最后将fps计算公式中的刷新率同步改为fpsHZ。总结本文着重讲解FrameTiming结构体在帧显示过程中的对应关系,说明获取精确帧的算法,最后完善获取帧的逻辑。一般来说,我拥有在Internet上可以找到的所有内容。在学习过程中遇到了两点,FrameTiming的结构和帧率的计算方法,感觉比较难理解。希望大家指出不足之处,谢谢!如果您觉得文章对您有帮助,点赞,收藏,关注,评论,一键四连支持,您的支持就是我创作最大的动力。??本文原文在听蝉公众号:编程黑板报,欢迎关注原创技术文章,第一时间推送??PS:本文所有源码获取方式:公众号后台回复“fps”参考链接如何编码获取FlutterAPP的FPS-Yrom'sFlutter如何更准确的获取FPS|Flutter性能计算之FlutterFPS计算-简书allenymt/flutter_fps:flutterFps的两种监控方案