跳到主要内容

性能分析

性能分析是分析应用程序的性能、资源使用情况和行为,以识别潜在瓶颈或效率低下的过程。值得使用性能分析工具来确保你的应用程序在不同的设备和条件下都能流畅运行。

对于 iOS,Instruments 是一个非常宝贵的工具,而在 Android 上,你应该学习使用 Android Studio Profiler

但首先,确保开发模式已关闭! 你应该在应用程序日志中看到 __DEV__ === false, development-level warning are OFF, performance optimizations are ON

使用系统跟踪分析 Android UI 性能

Android 支持 10k+ 不同的手机,并且被通用化以支持软件渲染:框架架构和跨多种硬件目标进行通用的需求不幸地意味着相对于 iOS,你获得的免费福利更少。但有时,有些事情你可以改进 —— 而且很多时候根本不是原生代码的错!

调试这种卡顿的第一步是回答一个基本问题:在每个 16 毫秒帧期间,你的时间都花在哪里了。为此,我们将使用 Android Studio 中内置的系统跟踪性能分析器

1. 收集跟踪记录

首先,通过 USB 将出现你想要调查的卡顿的设备连接到你的计算机。在 Android Studio 中打开你的项目的 android 文件夹,在右上角窗格中选择你的设备,然后以可分析配置文件的方式运行你的项目

当你的应用程序以可分析配置文件的方式构建并在设备上运行时,让你的应用程序到达你想要分析的导航/动画之前的状态,然后在 Android Studio Profiler 窗格中启动“捕获系统活动”任务

一旦跟踪开始收集,执行你关心的动画或交互。然后按“停止录制”。你现在可以直接在 Android Studio 中检查跟踪记录。或者,你可以在“过去录制”窗格中选择它,按“导出录制”,然后在像 Perfetto 这样的工具中打开它。

2. 读取跟踪记录

在 Android Studio 或 Perfetto 中打开跟踪记录后,你应该看到类似这样的内容

Example

提示

使用 WASD 键进行平移和缩放。

确切的 UI 可能会有所不同,但无论你使用什么工具,以下说明都适用。

启用 VSync 高亮显示

选中屏幕右上角的此复选框以高亮显示 16 毫秒帧边界

Enable VSync Highlighting

你应该看到如屏幕截图中的斑马条纹。如果你没有看到,请尝试在其他设备上进行性能分析:已知三星在显示 VSync 方面存在问题,而 Nexus 系列通常非常可靠。

3. 查找你的进程

滚动直到你看到你的包名(的一部分)。在这种情况下,我正在分析 com.facebook.adsmanager,由于内核中愚蠢的线程名称限制,它显示为 book.adsmanager

在左侧,你将看到一组线程,这些线程对应于右侧的时间线行。对于我们的目的,我们关心一些线程:UI 线程(其名称是你的包名或 UI 线程),mqt_jsmqt_native_modules。如果你在 Android 5+ 上运行,我们还关心渲染线程。

  • UI 线程。 这是标准 Android 测量/布局/绘制发生的地方。右侧的线程名称将是你的包名(在我的例子中是 book.adsmanager)或 UI 线程。你在此线程上看到的事件应该看起来像这样,并且与 ChoreographertraversalsDispatchUI 有关

    UI Thread Example

  • JS 线程。 这是 JavaScript 执行的地方。线程名称将是 mqt_js<...>,具体取决于你的设备上的内核的配合程度。如果它没有名称,要识别它,请查找类似 JSCallBridge.executeJSCall 等内容

    JS Thread Example

  • 原生模块线程。 这是执行原生模块调用(例如 UIManager)的地方。线程名称将是 mqt_native_modules<...>。为了在后一种情况下识别它,请查找类似 NativeCallcallJavaModuleMethodonBatchComplete 的内容

    Native Modules Thread Example

  • 奖励:渲染线程。 如果你正在使用 Android L (5.0) 及更高版本,你的应用程序中还将有一个渲染线程。此线程生成用于绘制 UI 的实际 OpenGL 命令。线程名称将是 RenderThread<...>。为了在后一种情况下识别它,请查找类似 DrawFramequeueBuffer 的内容

    Render Thread Example

识别罪魁祸首

一个流畅的动画应该看起来像这样

Smooth Animation

颜色的每次变化都是一个帧 —— 记住,为了显示一个帧,我们所有的 UI 工作都需要在该 16 毫秒周期结束前完成。请注意,没有线程的工作接近帧边界。像这样渲染的应用程序以 60 FPS 渲染。

但是,如果你注意到卡顿,你可能会看到类似这样的情况

Choppy Animation from JS

请注意,JS 线程几乎一直在执行,并且跨越了帧边界!此应用程序未以 60 FPS 渲染。在这种情况下,问题在于 JS

你也可能看到类似这样的情况

Choppy Animation from UI

在这种情况下,UI 和渲染线程是工作跨越帧边界的线程。我们在每个帧上尝试渲染的 UI 需要完成太多工作。在这种情况下,问题在于正在渲染的原生视图

此时,你将获得一些非常有用的信息,为你的下一步提供参考。

解决 JavaScript 问题

如果你确定了 JS 问题,请在你正在执行的特定 JS 中查找线索。在上面的场景中,我们看到 RCTEventEmitter 每帧被调用多次。以下是上面跟踪记录中 JS 线程的放大图

Too much JS

这似乎不太对劲。为什么它被如此频繁地调用?它们实际上是不同的事件吗?这些问题的答案可能取决于你的产品代码。而且很多时候,你会想研究 shouldComponentUpdate

解决原生 UI 问题

如果你确定了原生 UI 问题,通常有两种情况

  1. 你尝试在每帧绘制的 UI 涉及 GPU 上过多的工作,或者
  2. 你在动画/交互期间构建新的 UI(例如,在滚动期间加载新内容)。

GPU 工作过多

在第一种情况下,你将看到一个跟踪记录,其中 UI 线程和/或渲染线程看起来像这样

Overloaded GPU

请注意,在 DrawFrame 中花费了很长时间,并且跨越了帧边界。这是等待 GPU 排空上一帧的命令缓冲区所花费的时间。

为了缓解这种情况,你应该

  • 研究对正在动画/转换的复杂静态内容使用 renderToHardwareTextureAndroid(例如,Navigator 滑动/alpha 动画)
  • 确保你没有使用 needsOffscreenAlphaCompositing,默认情况下它是禁用的,因为它在大多数情况下会大大增加 GPU 的每帧负载。

在 UI 线程上创建新视图

在第二种情况下,你将看到更像这样的情况

Creating Views

请注意,首先 JS 线程思考了一会儿,然后你看到在原生模块线程上完成了一些工作,然后是在 UI 线程上进行昂贵的遍历。

除非你能够将创建新 UI 推迟到交互之后,或者你能够简化你正在创建的 UI,否则没有快速缓解这种情况的方法。react native 团队正在为此开发基础设施级别的解决方案,这将允许在主线程之外创建和配置新 UI,从而使交互继续流畅进行。

查找原生 CPU 热点

如果问题似乎出在原生端,你可以使用 CPU 热点分析器来获取有关正在发生的事情的更多详细信息。打开 Android Studio Profiler 面板并选择“查找 CPU 热点(Java/Kotlin 方法录制)”。

选择 Java/Kotlin 录制

确保你选择“查找 CPU 热点 (Java/Kotlin 录制)”而不是“查找 CPU 热点(调用堆栈采样)”。它们具有相似的图标,但做的事情不同。

执行交互并按“停止录制”。录制是资源密集型的,因此请保持交互简短。然后,你可以检查 Android Studio 中的结果跟踪记录,或者导出它并在像 Firefox Profiler 这样的在线工具中打开它。

与系统跟踪不同,CPU 热点分析速度较慢,因此它不会为你提供准确的测量结果。但是,它应该让你了解正在调用哪些原生方法,以及在每个帧期间按比例花费的时间。