案例研究:Gmail Wear OS 团队如何将其应用启动时间提高 50%

应用启动代表着应用给用户的第一印象。而用户也不愿意等待,所以您需要确保应用能够快速启动。为了向您展示真实的应用开发团队如何发现和诊断其应用启动方面的问题,以下是 Gmail Wear OS 团队的做法。

Gmail Wear OS 团队进行了优化工作,特别关注应用启动和运行时渲染性能,以满足其团队的应用性能标准。不过,即使您没有针对具体目标阈值,只要花些时间进行研究,几乎总有改进应用启动的余地。

捕获跟踪记录并查看应用启动

如需开始分析,请捕获包含应用启动数据的轨迹,以便在 Perfetto 或 Android Studio 中进一步检查。本案例研究使用 Perfetto,因为它可以向您展示设备系统中发生的情况,而不仅仅是您的应用。当您在 Perfetto 中上传轨迹时,您会看到如下所示的内容:

图 1. Perfetto 中跟踪记录的初始视图。

由于重点是改进应用启动,因此请找到 Android App Startups 自定义指标所在的行;这有助于将该行固定到视图顶部,方法是点击将鼠标悬停在该行时显示的图钉图标 。您在 Android App Startups 行看到的横条(即 Slice)表示应用启动所涵盖的时间范围,直到第一个应用帧绘制到屏幕上,因此您应该查找该处的问题或瓶颈。

突出显示的“Android App Startups”行,其中突出显示了用于固定的选项。
图 2. 将 Android App Startups 自定义指标固定到信息中心顶部,以便更轻松地进行分析。

请注意,即使您使用的是 reportFullyDrawn()Android App Startups 指标也表示初步显示所用时间。如需确定完全显示所用时间,请在 Perfetto 搜索框中搜索 reportFullyDrawn()

检查主线程

首先,查看主线程上发生的情况。主线程非常重要,因为它通常是所有界面渲染发生的位置;当主线程被阻塞时,无法进行绘制,并且应用看起来会卡住。因此,您需要确保主线程上没有发生长时间运行的操作。

如需查找主线程,请找到包含应用软件包名称的行,并将其展开。与软件包同名的两行(通常是该部分中的前两行)表示主线程。在两个主线程行中,第一行表示 CPU 状态,第二行表示跟踪点。固定 Android App Startups 指标下方的两个主线程行。

已固定“Android App Startups”和主线程行。
图 3. 固定 Android App Startups 自定义指标下方的主线程行,以帮助分析。

处于可运行状态和 CPU 争用所花的时间

如需获取应用启动期间 CPU 活动的汇总视图,请在主线程上拖动光标以捕获应用启动时间范围。随即显示的 Thread States 面板会显示所选时间范围内每种 CPU 状态所花的总时间。

查看处于 Runnable 状态的时间。当线程处于 Runnable 状态时,可以执行工作,但未调度任何工作。这可能表明设备负载过重,无法调度高优先级任务。用户可见的顶层应用的调度优先级最高,因此空闲的主线程通常表示应用中的密集型进程(如动画渲染)正在与主线程争用 CPU 时间。

在“Thread State”面板中突出显示了主线程,其中总时间处于不同状态。
图 4. 评估 RunnableRunning 状态的相对时间,以初步了解存在多少 CPU 争用。

Runnable 状态的时间与 Running 状态的时间比率越高,出现 CPU 争用的可能性越大。以这种方式检查性能问题时,请首先关注运行时间最长的帧,然后尝试较小的帧。

在分析处于 Runnable 状态所用时间时,请考虑设备硬件。由于所示应用正在具有两个 CPU 的穿戴式设备上运行,因此与我们观察的是具有更多 CPU 的设备相比,预期该应用在 Runnable 状态下花费的时间更长,并且与其他进程的 CPU 争用更多。虽然在 Runnable 状态上花费的时间比典型手机应用的预期多,但在穿戴式设备环境中是可以理解的。

OpenDexFilesFromOat*停留的时间

现在,检查在 OpenDexFilesFromOat* 中花费的时间;在跟踪记录中,它与 bindApplication 切片同时发生。此 Slice 表示读取应用的 DEX 文件所花费的时间。

阻塞的 binder 事务

接下来,检查 binder 事务。binder 事务表示客户端和服务器之间的调用:在这种情况下,应用(客户端)使用 binder transaction 调用 Android 系统(服务器),服务器用 binder reply 进行响应。请确保应用不会在启动期间进行不必要的 binder 事务,因为这会增加 CPU 争用的风险。如果可以的话,请将涉及 binder 调用的工作推迟到应用启动期结束后。如果需要进行 binder 事务,请确保这些事务的时间不会超过设备的 Vsync 刷新率

在这种情况下,第一个 binder 事务(通常与 ActivityThreadMain 切片同时发生)似乎太长了。如需详细了解可能发生的情况,请按以下步骤操作:

  1. 如需查看关联的 binder 回复,并详细了解如何确定 binder 事务的优先级,请点击所需的 binder 事务切片。
  2. 如需查看 binder 回复,请转到 Current Selection 面板,然后点击 Meeting thread 部分下的 binder 回复Thread 字段还会告诉您,如果您想手动转到某个线程,该线程会发生 binder 回复,该线程将位于另一个进程中。系统会显示一行连接 binder 事务和回复的语句。

    有一行连接 binder 调用和回复。
    图 5.确定应用启动期间发生的 binder 事务,并评估是否可以推迟这些事务。
  3. 如需了解系统服务器如何处理此 binder 事务,请将 Cpu 0Cpu 1 线程固定到屏幕顶部。

  4. 通过查找包含 binder 回复线程名称的切片来查找处理 binder 回复的系统进程,在本例中为“Binder:687_11 [2542]”。点击相关系统进程可获取有关 binder 事务的更多信息。

我们来看一下这个与 CPU 0 上发生的相关 binder 事务关联的系统进程:

结束状态为“Runnable (Preempted)”的系统进程。
图 6. 系统进程处于 Runnable (Preempted) 状态,这表明出现了延迟。

结束状态显示 Runnable (Preempted),表示进程因 CPU 正在执行其他操作而延迟。如需了解它被抢占了什么,请展开 Ftrace Events 行。在随即显示的 Ftrace 事件标签页中,滚动浏览并查找与感兴趣的 binder 线程“Binder:687_11 [2542]”相关的事件。在系统进程被抢占前后,发生了两个包含参数“decon”的系统服务器事件,这意味着它们与屏幕控制器有关。听起来很合理,因为显示控制器会将帧放在屏幕上 - 这是一项重要任务!让事件保持原样。

突出显示了与相关 binder 事务关联的 FTrace 事件。
图 7. FTrace 事件表示 binder 事务因屏幕控制器事件而延迟。

JIT activity

如需调查即时编译 (JIT) 活动,请展开属于您的应用的进程,找到两个“Jit 线程池”行,并将它们固定到视图顶部。由于此应用在应用启动过程中会从基准配置文件中受益,因此在绘制第一帧之前,几乎不会发生 JIT activity,这一点以第一个 Choreographer.doFrame 调用结束表示。不过,请注意启动缓慢的原因 JIT compiling void,这表示在标记为 Application creation 的跟踪点期间发生的系统 activity 会导致大量后台 JIT activity。如需解决此问题,请将第一帧绘制到基准配置文件后不久发生的事件,方法是将配置文件集合扩展至应用可供使用的位置。在许多情况下,您可以通过在基准配置文件集合 Macrobenchmark 测试的末尾添加一行代码来实现此目的,该测试会等待特定界面 widget 显示在屏幕上,表示屏幕已完全填充。

突出显示“Jit compiling void”切片的 Jit 线程池。
图 8. 如果您看到大量 JIT activity,请将基准配置文件展开至应用可供使用的位置。

成果

根据这项分析,Gmail Wear OS 团队做出了以下改进:

  • 由于他们在应用启动期间查看 CPU activity 时发现争用,因此他们替换了用于指示应用正在加载单张静态图片的旋转图标动画。他们还延长了启动画面以推迟闪烁状态(即用于指示应用正在加载的第二个屏幕状态),以释放 CPU 资源。这使应用启动延迟时间缩短了 50%。
  • 通过查看 OpenDexFilesFromOat*JIT activity 中花费的时间,他们启用了 R8 重写基准配置文件。这使应用启动延迟时间缩短了 20%。

下面是该团队针对如何高效地分析应用性能的一些建议:

  • 设置一个能够自动收集跟踪记录和结果的持续性流程。考虑使用基准化分析为您的应用设置自动跟踪。
  • A/B 测试用于发现您认为有助于改善情况的更改,如果无法改善,则拒绝。您可以使用 Macrobenchmark 库测量不同场景中的性能。

如需了解详情,请参阅以下资源: