爱彼迎自主开发的页面性能评分(PPS)旨在通过收集多种以用户为中心的性能指标,并将它们转化为一个 0-100 的综合评分,来描述丰富复杂的性能。在本文中,我们将深入介绍如何在Android 上定义和实现这些指标。请确保首先阅读概述文章,以熟悉我们的 PPS 指标和计算方法。
插桩
通用页面跟踪系统
爱彼迎的整个用户旅程被划分为不同的页面,每个页面都对其自己的PPS值进行测量。为了支持这个基于页面的性能跟踪系统,我们构建了一个标准化的基础架构,使工程师能够配置代表其功能的页面。
在Android上,每个页面都与一个Fragment相关联。每个Fragment都必须提供一个LoggingConfig对象,指定一个页面名称,以便在需要引用页面名称时能够检索到。我们在Fragment的生命周期中收集性能数据,并在Fragment暂停时才发出日志事件。
我们用一个通用的PageName枚举类型标识每个页面,并在所有平台上引用,从而一致地表示我们用户操作中的每个页面。
// SearchFragment overrides loggingConfig with a unique PageName.override fun loggingConfig() = LoggingConfig( pageName = PageName.SearchResults)捕捉用户感知的等待时间
我们新的页面性能评分(PPS)的一个关键特点是它衡量的是用户看到的等待时间。虽然我们早期的测量工作(在我们的概述博文中提到)是基于常见的交互时间(TTI)指标,该指标衡量代码执行时间和异步调用的深度。例如,PPS统计用户在屏幕上看到加载动画的时间,而TTI统计网络请求返回结果所需的时间以及构建视图模型所需的时间。我们认为PPS更能准确反映我们用户的性能体验。
为了获得可视化的等待时间,我们需要对具有加载状态的所有视图创建报告加载状态变化的API。所以我们创建了一个LoadableView接口。
// ALL views with a loading state must implement this interface, and set isLoading// whenever loading state changes.interface LoadableView { fun setIsLoading(isLoading: Boolean)}// Abstract base helper to obtain and log the current loading state of a view.abstract BaseLoadableViewHelper<out T : View>(protected val view: T) { // Observes loading state changes of a view and logs PPS events upon changes. var isLoading by observable(initialValue = false) { _, oldValue, newValue -> logPpsLoadingEvent(view, oldValue, newValue) }}我们提供了一些基本的类型,如 ViewGroup、TextView和ImageView,它们都实现了LoadableView接口。我们的开发人员只需继承这些基本类型,就可以自动实现插桩。
一个挑战是我们需要跟踪视图的可见性,因为如果一个视图在屏幕上可见度不足10%,我们不希望将其加载时间计入我们的测量中。计算每个视图的可见度百分比既频繁又递归。此外,我们的大多数视图都在RecyclerView中,我们必须确保在每次滚动事件中正确更新它们的可见性,同时保持RecyclerView的性能。我们设计了算法来减少这些计算的频率和复杂性,包括在RecyclerView内缓存可见性状态。
指标实现
首次布局时间(TTFL)
TTFL衡量用户在屏幕上看到任何内容之前需要等待多长时间。TTFL从fragment初始化开始,在fragment布局后的第一个onGlobalLayout事件结束。在这一点上,系统完成了片段fragment层次结构的加载、测量和布局。
较慢的TTFL通常表明在fragment的视图层次结构过于复杂,或者在在fragment初始化期间,UI线程被不必要的任务占用。
初始加载时间(TTIL)
TTIL衡量用户在屏幕上显示有意义的内容之前看到加载指示器(不包括单独测量的媒体加载)需要多长时间。TTIL从片段初始化开始,类似于TTFL,当屏幕上没有更多视图处于加载状态时结束。如果屏幕(片段)是静态的或被缓存,我们不显示加载指示器。在这种情况下,TTIL将与TTFL相同。
较慢的TTIL通常揭示了改善网络延迟或客户端渲染时间的机会。对于优化网络延迟,我们可以寻找慢的后端服务、大量的数据负载、未使用的缓存或优化程度较低的数据解析器。对于优化渲染时间,我们尽量遵循使用RecyclerView的最佳实践,在构建视图模型时避免进行繁重或递归的计算,并减少过度绘制等。
如上所述,有加载动画的视图可以继承实现了LoadableView接口的基本类型。该API会自动向我们的日志框架报告视图的加载状态变化。我们使用一个简单的计数器,在视图进入加载状态时递增,在数据加载完成后递减。当计数器为0时,我们知道屏幕上没有正在加载的视图。
@Synchronizedprivate fun changeViewCounterForFragment( fragment: BaseFragment, loadingType: UiElementLoadingType){ return when (loadingType) { UiElementLoadingType.Started -> { fragmentToLoadingMap[fragment]?.let { it.copy(viewCount = it.viewCount + 1) } } UiElementLoadingType.Ended -> { fragmentToLoadingMap[fragment]?.let { it.copy(viewCount = it.viewCount - 1) } } }}图 1:此GIF演示了TTFL(当显示带有Airbnb标志的灰色背景时标记)和TTIL(当加载动画被有意义的内容替换时标记)
主线程挂起(MTH)
当UI帧渲染时间过长时,用户会遇到屏幕冻结、卡顿和不流畅的情况。每个Android设备都有一个基于设备容量的目标帧刷新率。然而,当主线程过于繁忙时,设备的渲染速度会比其能力所支持的帧率慢。我们定义MTH为任何帧的渲染时间超过系统帧刷新率的两倍以上。
频繁的MTH表明主线程可能过载。应该将繁重的操作或计算移出UI线程,或者在内容渲染完成后再进行。MTH是通过Android系统报告的FrameMetrics来计算的。我们从系统获取帧刷新率,并用它来计算线程挂起的阈值。然后我们监听系统回调以接收FrameMetrics,如果帧持续时间超过我们的阈值,我们将差值(frameDuration - hangThreshold)记录为挂起。
额外加载时间(ALT)
ALT衡量的是初始加载后发生的任何等待时间,例如等待列表分页或在按下保存按钮后等待内容更新。ALT在TTIL标记后,视图进入加载状态时开始,并在不再显示加载动画时结束。ALT可以多次开始和结束,每次都被记录为单独的ALT。
改善ALT的机会通常在于预测和预加载后续的内容。平衡初始加载和后续加载的内容也可以提高整体PPS。
图 2:此GIF演示了ALT(当底部的加载动画替换为从网络加载的分页内容时标记)
富内容加载时间(RCLT)
RCLT衡量用户在看到占位符或加载动画后,直到图像、视频或某些富媒体内容完全显示出来所需的时间。ImageView和其他富媒体容器实现了相同的LoadableView API,将加载状态变化报告给PPS日志记录器。
为了改善RCLT,我们致力于减小图像大小,改善图像缓存,优化图像格式和提供方式,有策略地安排加载尚未显示在屏幕上的富内容,并选择高性能的流媒体库等。
图 3:此GIF演示了RCLT(当占位符被从网络加载的实际图像替换时标记)
结论
我们成功在Android上构建了一个工具化框架,以捕捉更丰富和以用户为中心的性能指标,遵循Airbnb在Web和原生平台上的页面性能评分的相同设计原则。在这个框架和收集的数据基础上,我们做了插桩,用于监控整个应用的性能,设置了针对页面所有者的自动警报,简化了团队和组织层面的性能目标设定,并系统地跟踪和减轻了性能问题。
接下来,我们计划改进我们的工具化的细粒度和准确性,比如测量点击响应速度,更好地区分滚动期间的性能,并提供内置性能优化的基本类型。我们还将投入资源来构建工具,以提高调试能力,并通过集成测试实现早期的回归检测和预防。
PPS为我们的工程师和数据科学家提供了更好的洞察力和改进产品的方式。它也加强了我们对于精益求精文化的承诺。我们希望您也能在您的组织中用到这些思路。
作者:Luping Lin
译者:Harry Zhang
校对:Ming Shang, Keyao Yang
来源-微信公众号:爱彼迎技术团队
出处:https://mp.weixin.qq.com/s/xzWBx0jgHBc_1siZD7q9mg