通过ADB命令实时获取FPS帧率有两种方式,一种是
dump gfxinfo
,一种是dump SF --latency
。其他还可以通过三方APP GameBooster(Google PlayStore可以下载)打开实时FPS监控查看。
如果是查看动画是否丢帧,可以通过视频工具QuickTime Player逐帧查看需要测试的动画的拍摄视频。一般每4帧会出现一个新的动画,录制视频查看这个过程是有动画虚影的。
dumpsys gfxinfo
dumpsys是一款运行在设备上的Android工具,将 gfxinfo命令传递给dumpsys可在logcat中提供输出,其中包含各阶段发生的动画以及帧相关的性能信息。但是gfxinfo不统计SurfaceView。
步骤:
- 打开开发者选项中的HWUI呈现模式分析,选择“在adb shell dumpsys gfxinfo中”
- 在需要测试的界面获取包名,可以使用dump SF/activity获取
- 清空后台任务,操作UI滑动,然后执行获取
adb shell dumpsys gfxinfo < PACKAGE_NAME >
framestats信息和frame耗时信息通常为2s收集一次(一次120帧,一帧16ms,耗时约2s)。如果要重置所有计数器重新收集帧率数据,执行adb shell dumpsys gfxinfo < PACKAGE_NAME > reset
案例
例如测试我的设备一加六滑动桌面的帧率,执行上述的前两步:
1 | adb shell dumpsys activity top|grep ACTIVITY |
然后滑动桌面执行adb shell dumpsys gfxinfo net.oneplus.launcher
,获取到128帧的数据。
1 | > adb shell dumpsys gfxinfo net.oneplus.launcher |
解析:
- Draw:构建java显示列表DisplayList的时间,也就是执行每一个View的onDraw方法,创建或者更新每一个View的DisplayList对象的时间。
- Prepare:准备函数的执行耗时
- Process:小号在Android的2D渲染器执行显示列表的时间,view越多,要执行的绘制命令就越多,耗时越长
- Execture:消耗在排列每个发送过来的帧的顺序的时间.或者说是CPU告诉GPU渲染一帧的时间,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复。所以这个时间,一般都很短。
Draw + Prepare+Process + Execute = 完整显示一帧 ,这个时间要小于16ms才能保存每秒60帧。
参数framestats
如果需要获取详细的帧信息,可以使用Android 6引入的新参数framestats,执行adb shell dumpsys gfxinfo < PACKAGE_NAME > framestats
结果会打印额外的信息,以CSV格式输出。每一行代表应用程序生成的一帧。每一行的列数都相同,每列对应描述帧在不同的时间段的耗时情况。
1 | ---PROFILEDATA--- |
解析:
- flags:FLAGS列为’0’的行可以通过从FRAME_COMPLETED列中减去INTENDED_VSYNC列计算其总帧时间。如果非零,则该行应该被忽略,因为该帧的预期布局和绘制时间超过16ms,为异常帧。
- *IntendedVsync:帧的的预期起点。如果此值与VSYNC不同,是由于UI线程中的工作使其无法及时响应垂直同步信号所造成的;
- Vsync:花费在vsync监听器和帧绘制的时间(Choreographer frame回调,动画,
View.getDrawingTime()
等); - OldestInputEvent:输入队列中最旧输入事件的时间戳,如果没有输入事件,则输入Long.MAX_VALUE。此值主要用于平台工作,对应用程序开发人员的用处有限。
- NewestInputEvent:输入队列中最新输入事件的时间戳,如果帧没有输入事件,则为0。此值主要用于平台工作,对应用程序开发人员的用处有限。然而,通过查看(FRAME_COMPLETED - NEWEST_INPUT_EVENT),可以大致了解应用程序添加的延迟时间。
- HandleInputStart:将输入事件分派给应用程序的时间戳(deliverInputEvent函数)。通过查看这段时间和ANIMATION_START之间的时间,可以测量应用程序处理输入事件的时间。
如果这个数字很高(> 2ms),这表明程序花费了非常长的时间来处理输入事件
。例如View.onTouchEvent(),也就是说此工作需要优化,或者分发到不同的线程。请注意,某些情况下这是可以接受的,例如发起新活动或类似活动的点击事件,并且此数字很大。 - AnimationStart:运行Choreographer注册动画的时间戳。通过查看这段时间和PERFORM_TRANVERSALS_START之间的时间,可以确定评估运行的所有动画器(ObjectAnimator,ViewPropertyAnimator和常用转换器)需要多长时间。
如果此数字很高(> 2ms),请检查您的应用是否编写了自定义动画以确保它们适用于动画。
- PerformTraversalsStart:计算
PERFORM_TRAVERSALS_STAR-DRAW_START
,则可以获取到布局和测量阶段完成的时间。(注意,在滚动或动画期间,希望这应该接近于零) - DrawStart:performTraversals的绘制阶段开始的时间。这是录制任何无效视图的显示列表的起点。这和SYNC_START之间的时间是在树中所有无效视图上调用View.draw()所花费的时间。
- SyncQueued:同步请求发送到RenderThread的时间。如果此时间和SYNC_START之间的时间很长(> 0.1ms左右),则意味着RenderThread忙于处理不同的帧。在内部,这被用来区分帧做了太多的工作,超过了16ms的预算,由于前一帧超过了16ms的预算,帧被停止了。
- SYNC_START:绘图的同步阶段开始的时间。如果此时间与
ISSUE_DRAW_COMMANDS_START
之间的时间很长(> 0.4ms左右),则通常表示有许多新的位图必须上传到GPU。 - IssueDrawCommandsStart:硬件渲染器开始向GPU发出绘图命令的时间。这段时间和
FRAME_COMPLETED
之间的时间间隔显示了应用程序正在生产多少GPU。像这样出现太多透支或低效率渲染效果的问题。 - SwapBuffers:eglSwapBuffers被调用的时间。
- *FrameCompleted:帧的完整时间。花在这个帧上的总时间可以通过
FRAME_COMPLETED - INTENDED_VSYNC
来计算。
这些数据可以直接通过修改开发者选项的HWUI呈现模式分析为在屏幕显示为条形图
,如图:
dumpsys SurfaceFlinger –latency
adb shell dumpsys SurfaceFlinger --latency LayerName
命令主要用于获取游戏/视频应用的fps数据。
1 | 16666666 |
第一行数据,表示刷新的时间间隔refresh_period,我的机器打印出来的间隔期是。即Dump SF里面的VSYNC period。
剩下的127行(127帧)数据分为三部分,每一列表是一种类型。
- 第一列:表示应用绘制图像的时间点
- 第二列:SF将帧提交给H/W(硬件)绘制之前的垂直同步时间。
- 第三列:在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。s
计算方式:一般打印出来的数据是129行(部分机型打印两次257行,但是第一部分是无效数据,取后半部分),取len-2的第一列数据为end_time,取len-128的第一列数据为start_time。
fps = 127/((end_time - start_time) / 1000000.0)
除以1000000是因为命令打印出来的是纳秒单位,要转为毫秒进行计算,127就是因为命令一次打印出来127帧的数据而已。