性能测量和分析示例

这些示例展示了如何使用系统跟踪与 Macrobenchmark,以及内存分析,来测量和改进某些类型的性能问题。

使用 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. 内存分析器显示垃圾回收事件。

一旦确定了导致内存压力的用例,就开始分析根本原因。

2. 诊断内存压力热点

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

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

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

按类排序

当您想要查找生成应该从内存池缓存或重用对象的类的时,按类排序很有用。

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

按调用栈排序

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

浅层大小与保留大小

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

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

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

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

3. 衡量优化的影响

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

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

  • 如果应用程序没有持续的内存压力,则应用程序因内存不足问题而被终止的次数会减少。
  • 减少 GC 可以改善卡顿指标。这是因为 GC 会导致 CPU 争用,这可能导致渲染任务在 GC 发生时被延迟。