性能衡量和分析的示例

这些示例展示了如何使用宏基准测试中的系统跟踪以及内存分析来衡量和改进某些类型的性能问题。

使用 systrace 调试应用程序启动

调试启动时间时,建议使用 systrace 日志。Systrace 是一个使用预先检测的代码输出某些事件发生时需要多长时间的系统。这些跟踪使您能够查看应用程序中甚至系统中其他进程中正在发生的事情。Android 平台和 Jetpack 库在应用程序中的许多关键事件周围都进行了检测,并且会相应地记录这些事件。您还可以使用自己的自定义跟踪检测您的应用程序,这些跟踪将显示在相同的 systrace 可视化工具中,以提供应用程序中发生情况的总体情况。

使用 systrace 或 Perfetto

要详细了解基本的 systrace 用法,请观看以下视频:调试应用程序性能

为了分析启动时间,您必须首先了解启动过程中发生了什么。如果您想要比本页上解释的内容更详细的信息,有关应用启动时间的文档提供了应用程序启动过程的概述。

应用程序启动的阶段如下:

  • 启动进程
  • 初始化通用应用程序对象
  • 创建和初始化活动
  • 填充布局
  • 绘制第一帧

启动类型包含以下阶段:

  • 冷启动:当应用程序从引导后第一次启动或应用程序进程被用户或系统杀死后启动时,就会发生这种情况。启动会创建一个没有 保存状态 的新进程。
  • 暖启动:当应用程序已经在后台运行,但必须重新创建活动并带到前台时,就会发生这种情况。活动是在重用现有进程时重新创建,或者进程是在保存状态的情况下重新创建。Macrobenchmark 测试库支持使用第一种选项进行一致的暖启动测试。
  • 热启动:当进程和活动仍在运行,并且只需要被带到前台时,就会发生这种情况,可能需要重新创建一些对象,以及渲染新的前台活动。这是最短的启动场景。

建议您捕获 systrace 使用“开发者选项”中提供的设备上系统跟踪应用程序。如果您想使用命令行工具,Perfetto 可用于 Android 10(API 级别 29)及更高版本,而运行早期版本的设备应使用 systrace

请注意,“第一帧”这个术语有点用词不当,因为应用程序在创建初始活动后如何处理启动的方式可能会有很大差异。一些应用程序会在几帧内继续膨胀,而另一些应用程序甚至会立即启动到二级活动。

如果可能,我们建议您在应用程序的角度完成启动时包含一个 reportFullyDrawn 调用(适用于 Android 10 及更高版本)。

在这些系统跟踪中需要注意的一些事项包括

Monitor contention
图 1. 对受监视器保护资源的竞争会导致应用程序启动出现明显的延迟。

Synchronous binder transactions
图 2. 查找应用程序关键路径中的不必要事务。

Concurrent garbage collection
图 3. 并发垃圾收集很常见,影响相对较小,但如果您经常遇到这种情况,请考虑使用 Android Studio 内存分析器进行调查。

I/O at startup
图 4. 检查启动期间的 I/O,并查找长时间停顿。

关于图 4,请注意,其他进程在同一时间执行 I/O 可能会导致 I/O 争用,因此请确保其他进程未运行。

其他线程上的大量活动可能会干扰 UI 线程,因此请注意启动期间的后台工作。请注意,设备可能具有不同的 CPU 配置,因此并行运行的线程数量在不同设备之间可能有所不同。

还可以查看有关 常见卡顿来源 的指南

使用 Android Studio 内存分析器

Android Studio 内存分析器 是一个强大的工具,可减少可能由内存泄漏或错误的使用模式造成的内存压力。它提供对象分配和收集的实时视图。

要修复应用程序中的内存问题,您可以使用内存分析器跟踪垃圾收集发生的原因和频率,以及是否存在导致堆随时间推移不断增加的潜在内存泄漏。

分析应用程序内存可分解为以下步骤

1. 检测内存问题

要检测内存问题,请从为您的应用程序记录内存分析会话开始。接下来,查找内存占用不断增加的对象,最终触发垃圾收集事件。

Increasing object count
图 5. 内存分析器显示随时间推移对象分配增加。

Garbage collections
图 6. 内存分析器显示垃圾收集事件。{.:image-caption}

确定一个增加内存压力的用例后,开始分析根本原因。

2. 诊断内存压力热点

在时间轴中选择一个范围以可视化分配和浅层大小。

Visualize allocations and shallow size
图 7. 内存分析器显示时间轴中所选范围的分配和大小。

有多种方法可以对这些数据进行排序。以下部分提供了一些有关每种视图如何帮助您分析问题的示例。

按类别排列

按类别排列在您想要查找生成应该被缓存或从内存池中重复使用的对象的类时很有用。

例如,假设您看到一个应用程序每秒创建 2,000 个名为“Vertex”的类的对象。这将使分配计数每秒增加 2,000,您在按类别排序时会看到这一点。是否应该重复使用此类对象以避免生成垃圾?如果答案是肯定的,那么可能需要实现一个内存池。

按调用栈排列

按调用栈排列在存在分配内存的热点路径(例如在循环内或执行大量分配工作的特定函数内)时很有用。按调用栈查看将允许您看到这些分配热点。

浅层大小与保留大小

浅层大小仅跟踪对象本身的内存,因此它最适合跟踪主要由基元组成简单类。

保留大小显示由对象直接分配的总内存以及由对象单独引用的其他分配对象的内存。它用于跟踪由于需要分配其他对象而不是仅分配基元字段的复杂对象而导致的内存压力。要获取此值,请使用内存分析器创建内存转储。转储中分配的对象将被添加到显示中。

Full memory dump
图 8. 您可以通过单击内存分析器工具栏中的“转储 Java 堆”按钮随时创建内存转储。

added as a column
图 9. 创建内存转储会显示一列,显示该堆中的对象分配。

3. 衡量优化效果

一个易于衡量的内存优化改进是垃圾收集。当优化减少内存压力时,您应该看到更少的垃圾收集(GC)。要衡量这一点,请在分析器时间轴中衡量 GC 之间的时间。您应该看到内存优化后 GC 之间的持续时间更长。

这些内存改进的最终影响是

  • 如果应用程序没有持续的内存压力,应用程序将因内存不足问题而被杀死的频率降低。
  • 减少 GC 次数可改善卡顿指标。这是因为 GC 会导致 CPU 争用,这会导致渲染任务在 GC 发生时被推迟。