作者:黎磊(千诺)
APM 提供帧率的相关数据,即 FPS(Frames Per Second) 数据。FPS 在一定程度上反映了页面流畅程度,但 APM 提供的 FPS 并不是很准确。恰逢手淘低端机性能优化项目开启,亟需相关指标来衡量对滑动体验的优化,帧率数据探索实践就此拉开。
在探索实践中,我们遇到了许多问题:
- 高刷手机占比相对不低,影响整体 FPS 数据
- 非人为滑动数据参杂在 FPS 中,不能直接体现用户操作体验
- 计算平均数据时,卡顿数据被淹没在海量正常数据中,一次卡顿是否只影响一个 FPS 值还是一次用户操作体验?
经过一段时间的探索,我们沉淀下来了一些指标,其中包括:滑动帧率、冻帧占比、scrollHitchRate、卡顿帧率。除了相关帧率指标之外,为了更好的指导性能优化,APM 还提供了帧率主因分析,同时为了更好的定位卡顿问题,也提供了卡顿堆栈。
下面是 APM 基于平台的特性,对帧率相关探索实践的详细介绍,希望本文可以给大家带来一些帮助。
系统渲染机制
在介绍指标的实现之前,首先需要了解系统是如何做渲染的,只有知晓系统渲染机制,才能帮助我们更好的进行帧率数据计算处理。
渲染机制是 Android 中重要的一部分,其中又牵扯甚广,包括我们常说的 measure/layout/draw 原理、卡顿、过度绘制等,都与其相关。在这里我们主要是对渲染流程进行整体了解,知晓后续需要计算哪几部分、通过系统 API 得到了哪几部分,以便计算出目标数据。
渲染流程
我们都知道,当触发渲染后,会走到 ViewRootImpl 的 scheduleTraversals。这时,scheduleTraversals 方法主要是向 Choreographer 注册下一个 VSync 的回调。当下一个 VSync 来临时,Choreographer 首先切到主线程(传 VSync 上来的 native 代码不运行在主线程),当然它并不是直接给 Looper sendMessage,而是 msg.setAsynchronous(true) ,提高了 UI 的响应速率。
当切到主线程后,Choreographer 开始执行所有注册了这个 VSync 的回调,回调类型分为以下四种:
- CALLBACK_INPUT,输入事件
- CALLBACK_ANIMATION,动画处理
- CALLBACK_TRAVERSAL,UI 分发
- CALLBACK_COMMIT
Choreographer 会将所有的回调按类型分类,用链表来组织,表头存在一个大小固定的数组中(因为只支持这四种回调)。在 VSync 发送到主线程的消息中,就会一条链表一条链表的取出顺序执行并清空。
而在 scheduleTraversals 注册的就是 CALLBACK_TRAVERSAL 类型的 callback,这个 callback 中执行的就是我们最为熟悉的 ViewRootImpl#doTraversal() 方法,doTraversal 方法中调用了 performTraversals 方法,performTraversals 方法中最重要的就是调用了耳熟能详的 performMeasure、performLayout、performDraw 方法。
详细代码可以翻看: android.view.Choreographer 和 android.view.ViewRootImpl
从这里我们可以看到,想要上屏一帧数据,至少包括:VSync 切到主线程的耗时、处理输入事件的耗时、处理动画的耗时、处理 UI 分发(measure、layout、draw)的耗时。
然而,当 draw 流程结束,只是 CPU 计算部分结束,接下来会把数据交给 RenderThread 来完成 GPU 部分工作。
屏幕刷新
Android 4.1 引入了 VSync 和三缓冲机制,VSync 给予开始 CPU 计算的时机,以及 GPU 和 Display 交换的缓冲区的时机,这样有利于充分利用时间来处理数据和减少 jank。
上图中 A、B、C 分别代表着三个缓冲区。我们可以看到 CPU、GPU、显示器都能尽快拿到 buffer,减少不必要的等待。如果显示器和 GPU 现在都使用着一个 buffer,如果下一次渲染开始了,因为还有一个 buffer 可以用于 CPU 数据的写入,所以可以马上开始下一帧数据的渲染,例如图中第一个 VSync。
是不是引入三缓冲机制就没有任何问题呢,当我们仔细看上图可发现,数据 A 在第三个 VSync 来临时就已经准备好,随时可以刷新到屏幕上,到真正刷到屏幕却是第四个 VSync 来临。由此可知,三缓冲虽然有效利用了等待 VSync 的时间,减少了 ja