作者 / Google 和 Facebook 团队
撰稿 / Google Android 团队的 Kateryna Semenova 和 Facebook 团队的 Tim Trueman、Steven Harris、Subramanian Ramaswamy
简介
缩短应用的启动时间并非小事,我们必须深入了解其影响因素。今年,Google Android 团队和 Facebook 应用团队一直在合作研究这方面的量化指标,并共享优化方法,以改善应用启动情况。Google Android 的公开文档中包含了很多关于 应用启动优化 的信息。这里我们想进一步分享其在 Facebook 应用中的实践情况,以及哪些因素有助于改善应用启动性能。
现在,每个月有超过 29 亿人使用 Facebook。Facebook 帮助人们构建社区,并让世界更紧密地联系在一起。用户会在这里分享生活的瞬间,了解和讨论正在发生的事情,建立和培养人际关系,共同合作以创造收入机会。
Facebook 应用开发者则致力于确保用户享受最佳体验,并让应用在任意设备、任何国家/地区和不同网络条件下都能流畅运行。Google Android 团队和 Facebook 团队精诚合作,在应用启动时间的指标定义和最佳实践上达成共识,并在这里分享给大家。
从哪里开始
首先自然是测量应用的启动时间。您可借此获悉用户启动体验的健康程度,追踪启动时间恶化的情况,并计算进行改进需要投入的资源量。归根结底,您的启动时间需要与用户满意度、参与度或用户增长相关联,以确定投入的优先次序。
Android 定义了两个衡量应用启动时间的指标: 完全显示所用时间 (TTFD) 和 初步显示所用时间(TTID)。虽然您可以进一步将其划分为冷/暖启动时间,但本文不会解释它们之间的区别,而 Facebook 的方法是,衡量和优化与应用交互的所有用户所经历的启动时间 (有些是冷启动,有些是暖启动)。
完全显示所用时间 (Time-To-Full-Display, TTFD)
TTFD 会记录您的应用完成渲染并可供用户交互和使用时所需的时间,可能包括显示本地存储或来自网络上的内容所需的时间。如果网络较慢,这可能会花费一段时间,并会视用户的使用设备而有所差异。因此,我们有必要立即展示一些内容,让用户看到应用启动的进程,而这就要提到 TTID 了……
初步显示所用时间 (Time-To-Initial-Display, TTID)
TTID 会记录您的应用显示背景、导航、可快速加载的本地内容、加载较慢的本地或网络内容的占位块所需要的时间。TTID 应该是用户可以四处导航并前往其目标的所需时间。
不要改变太多: 有一件事需要注意,就是在 TTID 和 TTFD 之间应用内容的视觉变化问题,例如在页面里先展示的是已缓存的内容,然后在网络内容加载完成后突然切换页面内容。这种突然的变化可能会让用户感到不快和沮丧,所以请确保您的应用可在 TTID 期间显示足够有意义的内容,尽可能地向用户展示其将在 TTFD 期间看到的内容。
达成用户目标
用户访问您的应用是为了获取内容,这可能需要一段时间完成加载,而您希望应用可以尽快把这些内容呈现给他们。
Facebook 应用开发者专注于基于 完全显示所用时间 (TTFD) 的指标,包含显示所有内容和图像,因为这代表了用户访问应用的完整体验。开发者想要知道,网络加载内容和图像是否花费了较长时间,或者加载失败,以便让团队可以从头到尾改善整个启动体验。
良好的 TTID 和 TTFD 目标应当是多少?
Facebook 将启动时间指标设定为他们认为应用启动耗时 "不佳" 的百分比,即任何 TTFD 超过 2.5 秒的启动或启动失败的部分 (例如,图像无法加载或应用崩溃)。Facebook 致力于通过改进时间超过 2.5 秒的启动,使其摆脱 "不佳" 状态,以及修复导致启动失败的问题,从而降低启动时间 "不佳" 的比例。选择 2.5 秒是因为,研究表明,这对于 Facebook 用户来说很重要。这也与 Web Vitals 为网站建议的 最大内容绘制 (LCP) 指标相符。
与 TTID 相比,提供完整体验,尤其是用网络获取最近的内容,会让您的 TTFD 启动指标看起来相当缓慢。而这其实是一件好事!它反映了用户对您应用的真实体验。您对此所做的改进,可能会像 Facebook 那样,提高用户的应用使用率以及对其性能的认可。
测量 TTFD 的棘手程度可能会视您的应用而异。如果太难,不妨从 初步显示所用时间 (TTID) 着手。虽然由于占位块或图像的存在可能会导致无法量化部分内容的加载性能,但这依然是一个着手点,毕竟这部分也是用户日常与应用交互的内容 (虽然不是全部)。
检测 TTID
在 Android 4.4 (API 级别 19) 及更高版本中,logcat 提供了 "Displayed" 值,用于记录从启动进程到完成在屏幕上绘制相应 Activity 第一帧所经过的时间。
报告的日志行类似于以下示例:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
检测 TTFD
要检测 TTFD,只需在您的所有内容都在屏幕上显示后,在 Activity 中调用 reportFullyDrawn())。请确保包含替换占位符的任何内容,以及您渲染的任何图像 (务必计算图像本身显示的时间,而不仅是其占位符显示的时间)。在您调用 reportFullyDrawn() 后,就可以在 logcat 里看到它:
ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
来自 Facebook 应用开发者的建议
多年来,Facebook 应用开发者一直在为众多设备、平台以及国家/地区的数十亿用户优化应用。本节分享了 Facebook 应用开发者在优化应用启动时运用的一些关键经验。
- 先理解,再优化 - 在定义了好的启动指标后,您就应该用其检测应用的具体表现,这有助于理解应用的启动性能并设置改进优先级,为您的用户带来更好的体验。从量化检测入手,一来可以证明存在提升空间,二来可以确定重点努力的方向,并且在开始优化后能看到具体的改进效果。
- 首先修复崩溃 - 在您检测启动状况之后,请确保应用确实可以启动。启动时的崩溃是最让人沮丧的事情,也是让用户放弃您应用的最快方式,请优先判定和处理这些问题。
- 切莫忘记功能可靠性 - 另外,不要忘记功能可靠性。您的应用是否能迅速展示一些内容,但却未能加载所有内容,或者加载图像的耗时过长?您的应用可能启动得很快,但未能按用户要求运行 (比如,点击按钮不起作用),这些因素都会恶化用户体验。
- 以一致性为目标 - 与具备一致性但启动时间长于平均水平的性能表现相比,不一致的性能表现更令人沮丧。观察启动时间的长尾,查看是否存在缓解这些启动缓慢情况的修复措施或方法。请务必查看您的离线和有损网络启动性能的启动行为。
- 并行工作 - 大多数当代手机都有至少 4 个 CPU,所以有空间处理多任务!除非万不得已,否则不要阻塞主线程。将 I/O 和非关键路径移动到主线程之外运行。
- 延迟执行 - 在实现了可靠且一致的启动后,请查看您为显示首个可见画面的内容所做的一切,是否有一些工作是不必要的?在应用启动之后,请把与启动体验不直接相关的任何工作移除、延后、或者移到后台 (但是注意观察应用的响应能力,并将其作为一个控制指标)。试着让您应用的 onCreate() 尽量保持轻量。您还可使用 Jetpack App Startup 开发库,以便在应用启动时初始化组件。这样做时,请确保仍然加载所有启动活动所需的模块,并且注意在延迟加载的模块可用时不要造成闪烁。
- 显示进度,但是不要过多地改变界面 - 请不要在启动期间过度改变要展示给用户的内容。如果用户尝试点击内容,结果它却发生变化,并得到了错误的结果,这是十分令人沮丧的。这类似于 Web Vitals 中的 累积布局偏移 (CLS) 概念。对于时长不定的网络端加载,请略过启动画面,并显示异步加载的占位符。您可以考虑在这个内容区域使用不太显眼的 动画 来反映加载状态。确保具体加载的内容结构和占位结构尽可能地匹配,以便在内容加载完成后实现平滑过渡。
- 缓存 - 当用户第一次打开应用时,您可以为一些界面元素展示加载指示器。在用户下次访问您的应用时,您可在加载更多最新内容的同时,显示这些已缓存的内容。您是否曾在应用加载完成后,看到我们在 Facebook 的动态更新中展示从网络获取到的最新内容?如果可以,请将网络加载过程从启动中排除出去,这样可以加快速度,并实现更一致的启动性能体验。但是,正如下一点所建议的那样,显示已缓存的内容并不总是最佳做法,因此,我们要衡量并找到对用户更友好的要素。
- 快慢结合 - 新颖,相关,但显示速度稍慢的内容,比快速显示的过时内容更好。直接向您的用户展示最新的内容,比启动超级迅速,但在启动不久之后就得刷新内容要更有价值。您可以评估以下做法是否效果更好: 做出优化,以尽量快速地显示最新内容,并设置超时时间,以在网络较慢时显示较旧的内容;在网络离线时,直接显示既有的内容。
- 一致的会话开始界面 - 在您的应用长时间处于后台后,您可能会发现,将用户重置到您的主内容界面是很好的做法。应用可以在设备的内存中保留很长时间。
- 查看内部工作原理 - 如果您 跟踪 并切实查看了启动期间执行的内容,或者干脆使用调试器,您可能会有让人惊喜的新发现!在充分了解了启动的关键路径后,您就可以高效地优化应用性能。在发现了具有最大提升空间的要素后,就在该方面做出投入。
- 确保能轻松实现正确的结果 - 开发者有时会使用并非最优的模式和架构,因为做事情的方法太多了。请放心大胆地整合您应用中使用的模式,然后加以优化,以便轻松选择合适方法,从而完成任务并让其高效运行。即时代码执行 (eager code execution) 模式就是一个很好的例子: 如果您正在执行第一次全屏绘制后才需要出现的内容代码,那么性能表现肯定会遭受损害。您不妨采用延迟执行的模式,仅在启动的关键路径遭到阻塞时,再以即时执行方式运行代码。
Google Android 团队给出的建议
Google Android 团队关于衡量和优化应用启动的建议请查阅官方文档 "应用启动时间"。
本节总结了一些适用于所有 Android 应用开发者且与上述 Facebook 建议相关的要点。
- TTID 和 TTFD 是应用启动的重要指标。Google Android 会在 Play 管理中心 按照 TTID 对应用进行排名。TTFD 是 TTID 的母集,因此 TTID 的任何改进措施都同时适用于这两个指标。
- 调用 reportFullyDrawn() 来报告 TTFD,让系统知道 Activity 已完成渲染。为改善应用启动速度,Android 系统会进行调整,以优先处理在调用 reportFullyDrawn()) 之前发生的工作。在您的应用处于完全可用状态时调用这个方法可以改善应用的启动时间。每个应用都应该使用这个 API!切莫忘记用其衡量应用表现情况。
- 用 Android Vitals 监控您应用的技术性能,有助于改善应用启动体验。通过 Play 管理中心,您可以查看各种数据以帮助您了解和改进应用的启动时间等性能表现。
- 我们知道,与在开发阶段修复错误相比,生产环境中的修复成本要高得多。这点也同样适用于性能方面。您可以借助 Jetpack Macrobenchmark: Startup 设置您的应用,以在早期使用本地性能测试衡量应用启动情况。
- 正如我们上面讨论的那样,量化检测是了解和优化启动的关键。Android 提供 系统跟踪 服务,可以帮助深入挖掘和诊断应用启动问题。
- 通过 Jetpack App Startup 开发库,我们可以直接高效地在应用启动时初始化组件。开发库和应用的开发者都可以使用此库来简化启动流程,并明确地设置初始化顺序。您可以使用此库设置在启动期间的什么时刻加载哪些组件。
- 影响应用启动的一个 典型问题 是在初始化时做了太多工作。比如,填充过大或复杂的布局、阻止屏幕绘制、加载和解码位图、垃圾回收等。
总结
本文介绍了一些关键的启动时间指标和优化最佳实践,以改善启动体验,帮助提升 Facebook Android 应用的用户参与度和使用率。本文还分享了 Google Android 团队建议的指标、开发库和工具。任何 Android 应用都可从本文分享的策略中受益。请大家动起手来,认真量化应用的启动情况,并为用户打造快速且令人愉悦的应用启动体验!
欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!