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

首先解释一下文章标题中的“帧渲染数据”。
“帧渲染数据”是指,完成渲染一帧的耗时。这是计算帧率的基础数据。

截止到 8.0 系统,安卓原生提供 API 或者自带的工具,甚至是统计性能的后台 Google Vitals,都没有提供直接获取帧率的功能。但是这些 API 或工具,直接或者间接的提供了获取每一帧渲染耗时的功能,开发者需要做二次计算才能得到帧率。至于为什么并给出帧率这个数据,我会在后文中给出自己的推测。

如果我们拿到了每一帧的耗时,我们就拿到了两个数据:某段连续时间 deltT 内渲染完成的帧数 n,那么 n / deltT 就是帧率。deltT 的选取上具有很大灵活性,deltT 应该设置为 1 秒,还是 2 秒?亦或是,n 固定为 1,相应的 deltT 设置该帧的耗时?不同的选取方法,得到的帧率值也不尽相同。比如第 n 帧耗时 tn,对 1/tn > 60 ? 60 : 1/tn 累加求和然后求均值,实际操作后会发现这种方案受某超时帧影响严重,如果某帧耗时较大,会大大拉低最后的 fps 值。

注意,虽然安卓原生系统没有直接提供帧率这个性能指标数据,但是某些第三方 Rom,比如魅族 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》](https://www.youtube.com/watch?v=xg2CH4aV2Fo))

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 在处理过多的用户输入时间,可以考虑将这些事件放到其他线程中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值