安卓帧渲染数据获取方式小结
来源:互联网 发布:软件502 bad gateway 编辑:程序博客网 时间:2024/06/17 15:25
- 两种策略四种方式
- ChoreographerFrameCallback
- 从 16ms 说起
- GraphicsBinder
- Profile GPU
- FrameMetrics
- ChoreographerFrameCallback
- 性能指标
- 更多好文
首先解释一下题目中的“帧渲染数据”。
“帧渲染数据”是指,完成渲染一帧的耗时。
如果我们拿到了每一帧的耗时,我们就拿到了两个数据:某段连续时间 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 可以获得每帧渲染耗时的详细数据,即渲染的每个阶段的耗时情况,方便开发者定位性能瓶颈。
帧渲染耗时柱状图
有两种方式可以查看柱状图:
- 在手机上查看,手机设置—开发者选项— GPU 呈现模式分析(或 GPU 显示配置文件)— 勾选“显示条形图”;
- 在 Android Studio 中查看,打开 GPU 呈现模式分析 — 勾选“在 adb shell dumpsys gfxinfo 中”,柱状图会显示在控制台的 GPU Monitor 区域;
5.0 及以下系统
4.3 系统上效果(在 GPU Monitor 中的效果,绿线表示 16ms,红线表示 33ms):
5.0 上效果(在 GPU Monitor 中的效果):
各个色块所代表的含义及该色块过大的可能原因:
6.0 及以上系统
在 GPU Monitor 中的效果:
各个色块所代表的含义及该色块过大的可能原因:
在 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 存储了如下数据:
比如使用 ActivityFrameMetrics 的效果:
该方法可以在 Application 统一完成初始化,无需各个页面单独设置。
优势:
1. 官方推荐方式;
2. 能够线上使用;
3. 不限于 120 帧;
劣势:
1. 7.0 及以上系统;
2. 要开启硬件加速;
性能指标
帧率可以衡量一个时间段内的的渲染性能,但是比较粗略。比如,在相同的时间内,掉了 500 帧,下面两种情况的帧率相同,但是用户体验却天壤之别:
- 每两帧掉一帧,即掉帧均匀分布,每帧的渲染耗时均在 17-32 ms,此时用户感受到相对流畅的页面滑动;
- 掉帧不均匀,掉帧集中出现在某段时间内,那么在这段时间内用户会觉得“ 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.
除了帧率,还有如下指标用于衡量页面滑动的流畅程度:
- 掉帧率
- 出现连续 2+ 掉帧的比例(Facebook) 遭遇掉帧率在 50%+ 的用户的比例(Slow Rendering,Engineer for High Performance with Tools from Android & Play 25:48)
- 遭遇 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
- 安卓帧渲染数据获取方式小结
- vue 后台数据获取与组件渲染、页面刷新数据消失的问题小结
- express 获取数据后渲染
- bootstrapTable动态渲染数据获取
- Ajax获取数据渲染到页面
- get方式数据获取
- 获取数据的方式
- 获取mxd中feature所在图层的渲染方式
- 模版+数据分离渲染方式的设计与实现
- 模版+数据分离渲染方式的设计与实现
- 模版+数据分离渲染方式的设计与实现
- jquery ajax加载数据前台渲染方式 不用for遍历
- 数据通信中数据的交换方式小结
- js数据存放及处理方式小结
- Android服务与数据存储方式小结
- js数据存放及处理方式小结
- 实时股票数据获取方式
- 实时股票数据获取方式
- Git学习笔记一:Git VS SVN
- C#委托的基本使用
- sharding-jdbc分库分表规则(1)-单表查询
- HDU
- 数据结构(1)--基本概念
- 安卓帧渲染数据获取方式小结
- 群晖阿里云OSS超爽体验
- 2017 ACM-ICPC 亚洲区(西安赛区)网络赛 C:sum<简单数学>
- 王爽 《汇编语言》 读书笔记 十三 int指令
- 1232:评委打分
- 1041考试座位号(查找元素)
- bootstrap-table 引入数据源-list 转 json格式
- java-nio之Selector组件
- 从零开始前端学习[4]:关于html5文本文件的一些简单介绍