安卓帧渲染数据获取方式小结

来源:互联网 发布:软件502 bad gateway 编辑:程序博客网 时间:2024/06/17 15:25

  • 两种策略四种方式
    • ChoreographerFrameCallback
      • 从 16ms 说起
    • GraphicsBinder
      • Profile GPU
      • FrameMetrics
  • 性能指标
  • 更多好文

首先解释一下题目中的“帧渲染数据”。
“帧渲染数据”是指,完成渲染一帧的耗时。

如果我们拿到了每一帧的耗时,我们就拿到了两个数据:某段连续时间 deltT 内渲染完成的帧数 n,那么 deltT / n 就是我们常说的帧率。

安卓原生系统没有直接提供帧率这个性能指标数据(魅族 M2 Note 手机上,Flyme 系统提供了帧率数据,此为个例),需要开发者自己计算得出。

下面讲下获取帧数据的策略和对应的实现方式。

两种策略四种方式

目前,获取帧数据的策略由 Choreographer.FrameCallback 和 GraphicsBinder 两种。

Choreographer.FrameCallback 的代表作是开源库 TinyDancer 和美团外卖的 Hertz(卡顿侦测)。

GraphicsBinder 的代表方式是 Profile GPU 和 FrameMetrics。

下面分别进行介绍。

Choreographer$FrameCallback

这种方式起源于 Facebook 在 DroidCon 的分享:《Road to 60fps》。在这之后,基于这个思路获取帧数据的各种开源库便如雨后春笋般出现了。

从 16ms 说起

多数设备的屏幕刷新频率是 60Hz,即每秒刷新 60 次,每隔 16.67 ms 刷新一次。如果下一帧能够在 16.67 ms 内渲染完成,每次刷新都能展示新的帧,在用户看来 app 流畅运行,否则第 N+1 次屏幕刷新将继续展示第 N帧(第 N+1 帧尚未渲染完成),将出现掉帧、卡顿现象。

但是需要注意的是,并不是所有的设备的刷新频率都是 60hz,相应的 60fps 对某些机型是不适用的,即某些机型上你永远无法达到 60fps(Galaxy core 2 33/60,Nexus 5 55/60,Nexus 4 49/60)。

这个思路牵涉两个核心类/接口:

  • Choreographer
  • Choreographer$FrameCallback

一次屏幕刷新完成后,将产生 VSync 信号并通知 Choreographer。
Choreographer 收到通知依次处理 Input、Animation、Draw,这三个过程都是通过 FrameCallback 回调的方式完成的。在 Draw 过程中,具体是执行 ViewRootImpl#performTraversals() 方法,完成视图树的 measure、layout、draw 流程。
而 FrameCallback#doFrame(long frameTimeNanos) 方法中可以得到 VSync 到来的时间戳,这样就能得到连续两帧开始渲染之间的间隔,将该值近似作为上一帧的渲染耗时。
实现 FrameCallback 接口,并通过 Choreographer#postFrameCallback() 方法将其跟 Input、Animation、Draw 这些回调一起塞入主线程的消息队列,就能源源不断的获取每一帧的渲染时间戳,每一个 VSync 的时间戳代表一帧,这样可以得到某段时间内渲染完成的帧数,二者相除即可得到帧率。


(上图摘自《Road to 60fps》)

GraphicsBinder

Profile GPU

通过 Profile GPU 可以获得每帧渲染耗时的详细数据,即渲染的每个阶段的耗时情况,方便开发者定位性能瓶颈。
帧渲染耗时柱状图
有两种方式可以查看柱状图:

  1. 在手机上查看,手机设置—开发者选项— GPU 呈现模式分析(或 GPU 显示配置文件)— 勾选“显示条形图”;
  2. 在 Android Studio 中查看,打开 GPU 呈现模式分析 — 勾选“在 adb shell dumpsys gfxinfo 中”,柱状图会显示在控制台的 GPU Monitor 区域;

5.0 及以下系统

4.3 系统上效果(在 GPU Monitor 中的效果,绿线表示 16ms,红线表示 33ms):
这里写图片描述

5.0 上效果(在 GPU Monitor 中的效果):
这里写图片描述

各个色块所代表的含义及该色块过大的可能原因:

色块 阶段 含义 Process 表示 CPU 在等待 GPU 完成渲染的耗时;该阶段耗时大表示 app 在 GPU 中做了过多的操作。 这里写图片描述 Execute Android 2d 渲染引擎利用 OpenGL 绘制和刷新 DisplayList 的耗时。该阶段耗时大表示 DisplayList 过多、执行时间过长。 这里写图片描述 XFer 上传 bitmap 到 GPU 的耗时。耗时过多表示 app 在加载过多的图形图片。 这里写图片描述 Update 创建和更新视图 DisplayList 的耗时。耗时过多可能是由于自定义 view 绘制过多,或者 onDraw() 方法里面操作过多。

6.0 及以上系统
在 GPU Monitor 中的效果:
这里写图片描述

各个色块所代表的含义及该色块过大的可能原因:

色块 阶段 含义 这里写图片描述 Swap Buffers 表示 CPU 在等待 GPU 完成渲染的耗时;该阶段耗时大表示 app 在 GPU 中做了过多的操作。 这里写图片描述 Command Issue Android 2d 渲染引擎利用 OpenGL 绘制和刷新 DisplayList 的耗时。该阶段耗时大表示 DisplayList 过多、执行时间过长。 这里写图片描述 Sync & Upload 上传 bitmap 到 GPU 的耗时。耗时过多表示 app 在加载过多的图形图片。 这里写图片描述 Draw 创建和更新视图 DisplayList 的耗时。耗时过多可能是由于自定义 view 绘制过多,或者 onDraw() 方法里面操作过多。 这里写图片描述 Measure / Layout 视图树执行 onMeasure() 和 onLayout() 方法的耗时;耗时过多表示视图树在这两个阶段效率较低。 这里写图片描述 Animation 执行动画的耗时。耗时过多可能是因为自定义动画运行效率较低,或者属性刷新出现异常状况。 这里写图片描述 Input Handling 执行输入时间回调的耗时。耗时过多可能是因为 app 在处理过多的用户输入时间,可以考虑将这些事件放到其他线程中进行处理。 这里写图片描述 Misc Time / VSync Delay 执行连续两帧之间的操作耗时。耗时过多可能是因为 UI 线程操作过多,可以考虑将这些操作放到其他线程中进行处理。

在 5.0 上执行 gfxinfo 命令,得到的即为渲染一帧所经过的各个阶段的耗时情况(单位毫秒):

adb shell dumpsys gfxinfo com.demo.appDraw Prepare Process Execute0.51 0.69 4.52 0.400.43 1.20 3.90 0.360.42 0.64 3.70 0.370.41 0.68 4.08 0.570.46 1.24 3.79 0.35

在 7.0 上执行:

adb shell dumpsys gfxinfo com.demo.appStats since: 115689258308387nsTotal frames rendered: 138Janky frames: 114 (82.61%)50th percentile: 19ms90th percentile: 150ms95th percentile: 200ms99th percentile: 300msNumber Missed Vsync: 40Number High input latency: 2Number Slow UI thread: 40Number Slow bitmap uploads: 2Number Slow issue draw commands: 70Draw Prepare Process Execute50.00 0.40 5.48 3.7850.00 0.77 1.66 3.9750.00 4.31 2.01 2.5950.00 5.29 9.59 4.3950.00 2.95 3.07 8.0650.00 1.76 1.93 3.12

在 7.0 系统上带上 framestats 参数可以获取最近的 120 帧数据:

adb shell dumpsys gfxinfo com.demo.app framestatsStats since: 101631537739178nsTotal frames rendered: 42Janky frames: 31 (73.81%)50th percentile: 17ms90th percentile: 19ms95th percentile: 21ms99th percentile: 34msNumber Missed Vsync: 2Number High input latency: 2Number Slow UI thread: 3Number Slow bitmap uploads: 0Number Slow issue draw commands: 27---PROFILEDATA---Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,0,101647039145922,101647039145922,101647018084000,101647034145922,101647039815217,101647041206884,101647041424071,101647041635530,101647042167821,101647044205842,101647045030842,101647051882405,101647055263134,0,101647054735049,101647054735049,101647039692000,101647049735049,101647055237613,101647056265738,101647056492821,101647056665738,101647057101676,101647057342821,101647058124071,101647066840738,101647074210530,0,101647071403801,101647071403801,101647050345000,101647066403801,101647071899592,101647074218342,101647074530321,101647074697509,101647075244905,101647075473030,101647076719384,101647082193342,101647090448030,0,101647089118048,101647089118048,101647072068000,101647084118048,101647089415738,101647090049071,101647090219905,101647090331884,101647090610009,101647090709488,101647091358446,101647095696988,101647107083967,0,101647105786017,101647105786017,101647093579000,101647100786017,101647106096988,101647106731363,101647106896988,101647107007405,101647107266780,101647132519905,101647133169905,101647137525113,101647140328759,---PROFILEDATA---

第一部分是卡顿的统计数据,包括掉帧率、不同分位值对应的耗时;第二部分(PROFILEDATA)是详细数据,即绘制一帧所经过的各个阶段的起始时间戳,最后一项减去第二项即为该帧的耗时(单位纳秒);

除了 framestats 参数,执行 reset 参数可以清楚帧数据缓存,重新开始记录帧数据。

FrameMetrics

从 7.0(API 24)开始,安卓 SDK 新增 OnFrameMetricsAvailableListener 接口用于提供帧绘制各阶段的耗时,数据源与 GPU Profile 相同。
回调接口为 Window.FrameMetrics:

public interface OnFrameMetricsAvailableListener {    void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation);}

FrameMetics 存储了如下数据:

阶段 含义(纳秒) 备注 ANIMATION_DURATION 动画耗时 COMMAND_ISSUE_DURATION 执行 OpenGL 命令和 DisplayList 耗时 DRAW_DURATION 创建和更新 DisplayList 耗时 FIRST_DRAW_FRAME 布尔值,标志该帧是否为此 Window 绘制的第一帧 一般忽略此帧 INPUT_HANDLING_DURATION 处理用户输入操作的耗时 INTENDED_VSYNC_TIMESTAMP 预期 VSync 到来的时间戳 API >=26 可用 LAYOUT_MEASURE_DURATION layout/measure 耗时 SWAP_BUFFERS_DURATION CPU 在等待 GPU 完成渲染的耗时 SYNC_DURATION 上传 bitmap 到 GPU 的耗时 TOTAL_DURATION 整帧渲染耗时 UNKNOWN_DELAY_DURATION 未知延迟 VSYNC_TIMESTAMP VSync 实际到来的时间戳 API >=26 可用

比如使用 ActivityFrameMetrics 的效果:

这里写图片描述

该方法可以在 Application 统一完成初始化,无需各个页面单独设置。

优势:
1. 官方推荐方式;
2. 能够线上使用;
3. 不限于 120 帧;

劣势:
1. 7.0 及以上系统;
2. 要开启硬件加速;

性能指标

帧率可以衡量一个时间段内的的渲染性能,但是比较粗略。比如,在相同的时间内,掉了 500 帧,下面两种情况的帧率相同,但是用户体验却天壤之别:

  1. 每两帧掉一帧,即掉帧均匀分布,每帧的渲染耗时均在 17-32 ms,此时用户感受到相对流畅的页面滑动;
  2. 掉帧不均匀,掉帧集中出现在某段时间内,那么在这段时间内用户会觉得“ app 卡死了,界面冻住了”,估计多数用户此时会杀掉 app;

因此,单纯通过帧率来衡量性能是不够严谨的,比如 Facebook 就用连续掉 2+ 帧的比例来衡量 fps,Jason Sendros 对此指标合理性的解释是( 视频《Road to 60fps》第 14:14 处):

1 frame drop is noticeable if you are staring at something and the rest of the app is buttery smooth. If you start where you are not super buttery smooth 1 frame drop is going to completely unnoticeable.
2 consecutive frame drops is a little bit noticeable and it’s kind of annoying.
3 frame drops gets a bit worse.
4 gets a little irritating.
By 5 frame drops you are not even sure if the app is responding to you when you are doing something for a short period of time and it’s just a really frustrating experience.

除了帧率,还有如下指标用于衡量页面滑动的流畅程度:

  1. 掉帧率
  2. 出现连续 2+ 掉帧的比例(Facebook) 遭遇掉帧率在 50%+ 的用户的比例(Slow Rendering,Engineer for High Performance with Tools from Android & Play 25:48)
  3. 遭遇 700ms+ 耗时帧占比大于 0.1% 的用户的比例(Frozen Frames,Engineer for High Performance with Tools from Android & Play 25:48)

这里写图片描述

更多好文

  • Road to 60fps
  • GPU Monitor
  • Analyzing with Profile GPU Rendering
  • Profile GPU Rendering Walkthrough
  • Testing UI Performance
  • Graphics architecture
  • 关于 android 通过 python 统计 fps
  • Speed up your app
  • Testing Android UI Performance
  • android-perf-testing
  • dumpsys 实现原理
  • 手机性能评测–2D场景
  • Engineer for High Performance with Tools from Android & Play
原创粉丝点击