在 Android Studio 中使用 CPU 性能分析器锁定界面卡顿

Android 的用户界面渲染是从应用中 “生成” 一帧然后显示在屏幕上,为了确保用户能够获得顺畅的交互体验,您的应用需要根据设备的刷新率完成每一帧的 “生成”。例如,Pixel 6 设备支持每秒最多渲染 90 帧,这就意味着应用的每一帧任务都要在 1000/90 = 11ms 内完成,如果没有,Android 框架就会将这一帧的渲染跳过,用户就会从视觉上感受到界面运行缓慢,我们称这种情况为 “卡顿”。

出现卡顿的原因有很多种,例如,由应用导致的卡顿或由 SurfaceFlinger 导致的卡顿。本文重点关注由应用导致的卡顿,以及 Android Studio 提供的用于发现和消除这些卡顿的工具,方法是在应用交互的过程中检查记录的轨迹来解决应用的性能问题。

注意: 本文所展示的是 Android Studio Chipmunk 更新后的卡顿检测界面,使用 Android 12 (API 级别 31) 或更高版本的真机或模拟器。

从实时交互记录轨迹

以下示例使用 GitHub 性能示例仓库中的 JankStatsSample 应用来展示如何使用 CPU 性能分析器锁定卡顿的原因:

  1. 打开 JankStatsSample 并运行应用。

  2. 打开 Android Studio 底部的 Profiler 标签页。

  1. 点击 Profiler 左侧窗格中的 + 图标启动新的性能分析会话,然后选择要运行性能分析器的设备名称和应用进程。

注意: 虽然可以分析 “可调试应用” (debuggable app),但推荐做法是分析 “可分析应用” (profileable app),因为分析 “可调试应用” 会导致性能开销显著增加。更多详细信息,请参阅关于可分析应用的 文档

  1. 点击 CPU 行。

  2. 选择 System Trace Recording,点击 Record

  1. 与应用互动以收集数据,然后点击 Stop 按钮。Android Studio 将显示 Display 窗格,其中可见卡顿帧,也可以选中 All Frames 复选框,让轨迹记录也显示非卡顿帧。

将鼠标指针悬停在帧上或点击帧,即可查看详细的帧信息。如果选中 All Frames 复选框,将有三种类型的帧。

  • 绿色帧

    不卡顿的正常帧

  • 红色帧

    由于应用进程运行时间超出预期并且错过了预期帧时长的截止时间,被视为卡顿的帧。从应用开发者的角度来看,可操作帧通常是红色帧

  • 黄色帧

    被视为缓冲区填充的帧,这表示应用会在呈现前一帧之前不断将帧发送到系统。这通常是前一帧卡顿所致,应用开发者面对黄帧无能为力。

选中 Lifecycle 复选框,可以切换显示/隐藏四个附加轨迹。

四个轨迹:

  • Application
  • Wait for GPU
  • Composition
  • Frames on display

Android Studio Bumblebee (2021.1.1) 稳定版 中已经加入了这些轨迹信息的显示。您可以在 文档 中查看每个轨迹的详细说明。

检查卡顿帧

接下来,我们来找出卡顿帧出现的原因。

  1. Janky frames 轨迹中选择一个卡顿帧,Display 窗格将突出显示相应的生命周期数据,Threads 窗格将突出显示相应的线程数据。

虚线 Deadline 代表截止时间。当帧的时长超过该截止时间时,该帧即被视为卡顿。

您还可以在 Android Studio 的右侧窗格中查看详细分析。

  1. 如果您看一下应用主线程相应的轨迹窗格,可以看到大量的时间花费在 “View#draw” 中。

此外,如详细分析窗格中的主线程状态所示,大量线程所处的状态是正在休眠。

  1. 我们看一下调用 View#draw 的代码。View#onDraw 在 JankyView 类中被 复写:
override fun onDraw(canvas: Canvas) {
  simulateJank()
  super.onDraw(canvas)
}

从 onDraw 调用的 simulateJank 方法 定义如下:

fun simulateJank(
    jankProbability: Double = 0.3,
    extremeJankProbability: Double = 0.02
) {
    val probability = nextFloat()

    if (probability > 1 - jankProbability) {
        val delay = if (probability > 1 - extremeJankProbability) {
            nextLong(500, 700)
        } else {
            nextLong(32, 82)
        }

        try {
            // 在分析器中通过轨迹使卡顿更易被锁定
            trace("Jank Simulation") {
                Thread.sleep(delay)
            }
        } catch (e: Exception) {
        }
    }
}

然后,您可以发现在 simulateJank 方法中调用了 Thread.sleep。显然,这是因为 JankStatsSample 应用是为了模拟卡顿而特别创建的。但值得注意的是,从卡顿帧的概览再到更详细的分析,可以找出实际代码。

注意: 虽然在这个示例中调用 Thread.sleep 明显有问题,但实际上,在优化实际应用代码时,您将面临更困难的决定。Microbenchmark 开发库 有助于衡量所做的更改能否达成预期效果:

注意: 系统轨迹显示的是由平台代码和属于应用的库捕获的多个部分,通常没有足够的信息。添加自定义轨迹能够改善这样的情况,添加的方法之一是使用 AndroidX Tracing 库中的 trace(“MySection”) { / this will be in the trace / }。

例如,此示例中的 trace(“Jank Simulation”) { … } 显示在相应线程的轨迹部分中。

如需了解关于读取轨迹和添加自定义轨迹的更多信息,请参阅 系统轨迹概述

**

  1. 将代码更改为在 onDraw 中不去调用 simulateJank 方法,然后查看是否仍然存在卡顿帧。
override fun onDraw(canvas: Canvas) {
  // simulateJank() 
  super.onDraw(canvas)
}

这一次,重新运行系统轨迹录制时,您会发现与应用的交互不再有卡顿帧啦。

加载保存的轨迹

您也可以遵循以下步骤,保存轨迹并后续再加载。保存并加载轨迹后,您可以比较不同版本的跟踪记录或者与他人共享。

注意: 您还可以使用 Macrobenchmark 库 获取系统轨迹:

  1. 按照从第 1 步至第 6 步的相同步骤,通过实时交互操作录制轨迹。

  2. 点击保存图标导出记录。

  1. 然后,导航到 + -> Load from file… 加载已保存的系统轨迹录制,选择上一步中导出的已保存文件。

  1. 选择要分析的进程。显示进程列表下拉菜单后,输入进程名称的一部分即可快速查找。

然后,您可以加载保存的轨迹,像从实时互动录制一样查看卡顿帧。

总结

本期内容就到这里了,从 Android Studio Chipmunk 开始,您能够看到更精准的性能分析数据,更快锁定应用卡顿。

请查看 文档 进一步了解 CPU 性能分析器的用法,也欢迎您前往 Android Studio 中的 “Help → Submit Feedback” 帮助我们改进工具。

您还可以在下方查看 Google I/O 大会上的视频演讲 —— Android 应用性能近期更新,其中涵盖了非常丰富的关于性能方面的主题,包括如何避免卡顿帧。

△ Android 应用性能近期更新

我们新发布了一个库,可用于检测和报告在用户设备上运行的应用卡顿,如果您希望了解更加详细的内容,欢迎查看 JankStats 文档

欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的 支持!