性能的衡量和分析示例

以下示例展示了如何结合使用系统跟踪、Macrobenchmark 以及内存性能分析来衡量并改善特定类型的性能问题。

使用 systrace 调试应用启动

建议您根据 systrace 日志来调试启动时间。systrace 系统可使用预插桩的代码,在特定事件发生时输出该事件所用的时间。您可通过这些跟踪记录了解自己应用内发生的情况,甚至了解整个系统中其他进程的情况。Android 平台和 Jetpack 库提供针对应用内多个关键事件的插桩,并且会相应地记录这些事件。您还可以使用自己的自定义跟踪记录(会显示在相同的 systrace 可视化工具中)对应用进行插桩处理,以全面了解应用内发生的情况。

使用 systrace 或 Perfetto

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

要分析启动时间,您必须先了解启动期间会执行哪些操作。如需了解本页内容以外的更多信息,请参阅关于应用启动时间的文档,简要了解应用的启动过程。

应用的启动过程包含以下几个阶段:

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

按启动类型可以分为以下几个阶段:

  • 冷启动:系统启动后或应用进程被用户或系统终止后,首次启动应用时。启动会创建一个没有已保存状态的新进程。
  • 温启动:应用已在后台运行,但必须重新创建 activity 并转到前台运行时。重新使用现有进程或使用已保存的状态重新创建进程时,会重新创建 activity。Macrobenchmark 测试库支持通过第一种方式使温启动测试保持一致。
  • 热启动:进程和 activity 仍在运行,而且只需转到前台运行时(可能会根据需要重新创建某些对象);以及渲染新的前台 activity 时。这是耗时最短的启动场景。

建议您通过开发者选项中提供的设备上系统跟踪应用捕获 systrace 跟踪记录。如果您想使用命令行工具,Perfetto 适用于 Android 10(API 级别 29)及更高版本,而搭载更低版本的设备应使用 systrace

请注意,“第一帧”这一用词不太恰当,因为不同的应用在创建初始 activity 后处理启动的方式可能存在巨大差异。有些应用会继续膨胀数帧,而有些应用则会立即启动到第二个 activity。

如果可能,建议您在从应用角度看已完成启动后添加一个 reportFullyDrawn 调用(适用于 Android 10 及更高版本)。

以下是在这些系统跟踪记录中需要关注的内容:

监视器争用
图 1. 争用受监视器保护的资源可能会导致应用的启动出现明显的延迟。

同步 binder 事务
图 2. 在应用的关键路径中查找不必要的事务。

并发垃圾回收
图 3. 并发垃圾回收很常见且影响相对较小,但如果您需要经常触发该操作,建议使用 Android Studio 内存性能分析器对其进行分析。

启动时的 I/O
图 4. 检查启动期间的 I/O 并确认是否存在长时间停顿。

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

其他线程上的重要 activity 可能会干扰界面线程,因此请留意启动期间的后台工作。请注意,不同设备的 CPU 配置可能会有所不同,因此不同设备上可并行运行的线程数可能也会有所不同。

另请查看常见的卡顿来源指南。

使用 Android Studio 内存分析器

Android Studio 内存性能分析器是一款功能强大的工具,可减小内存泄漏或模式使用不当可能会带来的内存压力。您可以通过该工具实时查看对象分配和回收情况。

若要修复应用中的内存问题,您可以使用内存分析器来跟踪垃圾回收的原因和频率,以及是否存在导致您的堆在一段时间内不断增加的内存泄漏问题。

应用内存性能的分析分为以下几个步骤:

1. 检测内存问题

如需检测内存问题,请先记录应用的内存性能分析会话。接下来,查找内存占用量不断增加且最终触发了垃圾回收事件的对象。

增加的对象数
图 5. 显示在一段时间内增加的所分配对象数的内存分析器。

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

确定会增加内存压力的使用场景后,开始分析根本原因。

2. 诊断内存压力热点

在时间轴中选择一个范围,以直观呈现分配情况和浅层大小。

直观呈现分配情况和浅层大小
图 7. 显示时间轴中所选时间范围内的分配情况和大小的内存性能分析器。

您可以通过多种方式对这些数据进行排序。以下内容通过一些示例说明了每个视图如何帮助您分析问题。

按类排列

如果您想查找哪些类正在生成本应从内存池中缓存或重用的对象,按类排列非常有用。

例如,假设某个应用每秒会使用名为“Vertex”的类创建 2,000 个对象。这会使分配数每秒增加 2,000 个,按类排序时,您会看到这一数值。是否会重新使用这些对象来避免产生垃圾?如果是,则可能需要实现内存池。

按调用堆栈排列

当存在用于分配内存的热路径(例如,在循环内或执行大量分配工作的特定函数内),按调用堆栈排列非常有用。按调用堆栈排列时可查看分配的热点。

浅层大小和保留大小

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

保留大小显示对象直接分配的总内存,以及分配的单独由该对象引用的其他对象。保留大小适用于跟踪因需要分配其他对象(而不仅仅是基元字段)的复杂对象导致的内存压力。如需获取此值,请使用内存性能分析器创建内存转储。相应堆中分配的对象会加到显示的数值中。

完整内存转储
图 8. 您随时可以点击内存分析器工具栏中的“Dump Java heap”按钮来创建内存转储。

已添加为列
图 9. 创建内存转储可显示一个会显示该堆中的分配情况的列。

3. 衡量优化的影响

垃圾回收是一种易于衡量的内存优化改进方式。如果优化可减小内存压力,您会发现垃圾回收 (GC) 量变少。若要衡量此指标,请衡量性能分析器时间轴中垃圾回收之间的时间间隔。执行内存优化后,您会发现垃圾回收之间的间隔时间变长。

这种内存优化方式的最终影响为:

  • 如果应用不常导致内存压力,因内存不足而导致应用终止运行的频率将降低。
  • 减少垃圾回收次数可以优化卡顿指标。这是因为垃圾回收会导致 CPU 争用,从而导致渲染任务在垃圾回收时出现延迟。