性能测量和分析示例

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

使用 systrace 调试应用启动

在调试启动时间时,我们建议使用 systrace 日志。Systrace 是一个系统,它使用预先检测的代码来输出某些事件发生时所需的时间。这些跟踪使您能够看到应用中,甚至系统中其他进程中正在发生什么。Android 平台和 Jetpack 库对应用中的许多关键事件都进行了检测,并相应地记录了这些事件。您还可以使用自己的自定义跟踪对应用进行检测,这些跟踪将显示在相同的 systrace 可视化工具中,从而全面了解应用中发生的情况。

使用 systrace 或 Perfetto

要了解有关 systrace 基本用法的更多信息,请参阅以下视频:调试应用性能

为了分析启动时间,您必须首先了解启动期间发生的情况。如果您想获取比本页面解释更详细的信息,请参阅应用启动时间文档,该文档概述了应用启动过程。

应用启动阶段包括:

  • 启动进程
  • 初始化通用应用对象
  • 创建并初始化 activity
  • 展开布局
  • 绘制第一帧

启动类型具有以下阶段:

  • 冷启动:当应用自启动以来首次启动,或自应用进程被用户或系统终止以来首次启动时发生。启动会创建一个没有保存状态的新进程。
  • 温启动:当应用已在后台运行,但 activity 必须重新创建并带到前台时发生。activity 在重用现有进程的同时重新创建,或进程在保存状态下重新创建。Macrobenchmark 测试库支持使用第一种选项进行一致的温启动测试。
  • 热启动:当进程和 activity 仍在运行,并且只需带到前台时发生,可能需要根据需要重新创建一些对象,并渲染新的前台 activity。这是最短的启动场景。

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

请注意,“第一帧”这个术语有点用词不当,因为应用在创建初始 activity 后处理启动的方式可能差异很大。有些应用会持续展开几帧,而另一些甚至会立即启动到次级 activity。

如果可能,我们建议您在应用完成启动时包含 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. 您可以随时通过点击内存分析器工具栏中的“Dump Java heap”(转储 Java 堆)按钮来创建内存转储。

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

3. 衡量优化效果

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

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

  • 如果应用不持续存在内存压力,则由于内存不足问题而被终止的频率会降低。
  • 更少的 GC 改善了卡顿指标。这是因为 GC 会导致 CPU 争用,这可能导致在 GC 发生时渲染任务被延迟。