跳到主要内容

性能分析

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

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

但首先,确保开发模式已关闭!

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

Android 支持 10000+ 种不同的手机,并且普遍支持软件渲染:框架架构以及需要在众多硬件目标上进行泛化的需求,不幸意味着相对于 iOS,你免费获得的优化更少。但有时,有些事情你可以改进——而且很多时候根本不是原生代码的错!

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

备注

独立的 systrace 工具已从 Android platform-tools 中移除。请改用 Android Studio Profiler,它提供了相同的功能且具有更好的用户界面。

1. 收集追踪记录

首先,通过 USB 将表现出你想要调查的卡顿现象的设备连接到电脑。在 Android Studio 中打开项目的 android 文件夹,在右上角窗格中选择你的设备,并 将你的项目作为可分析项目运行

当你的应用构建为可分析状态并在设备上运行时,将应用引导至你想要分析的自然过渡/动画之前的状态,然后在 Android Studio Profiler 窗格中启动 "捕获系统活动" 任务

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

2. 阅读追踪记录

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

示例

提示

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

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

启用 VSync 高亮

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

启用 VSync 高亮

你应该看到如上图所示的斑马纹。如果没有,尝试在不同的设备上进行分析:众所周知,三星在显示 vsync 方面存在问题,而 Nexus 系列通常非常可靠。

3. 找到你的进程

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

在左侧,你将看到一组对应于右侧时间轴行的线程。出于我们的目的,有几个线程我们关心:UI 线程(具有你的包名或名称 UI Thread)、mqt_jsmqt_native_modules。如果你在 Android 5+ 上运行,我们还关心 Render Thread。

  • UI 线程。 这是标准 android measure/layout/draw 发生的地方。右侧的线程名称将是你的包名(在我的例子中是 book.adsmanager)或 UI Thread。你在此线程上看到的事件应该看起来像这样,并与 ChoreographertraversalsDispatchUI 有关:

    UI 线程示例

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

    JS 线程示例

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

    原生模块线程示例

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

    Render Thread 示例

识别罪魁祸首

流畅的动画应该看起来像下面这样:

流畅的动画

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

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

JS 导致的卡顿动画

注意 JS 线程几乎一直在执行,并且跨过了帧边界!此应用没有以 60 FPS 渲染。在这种情况下,问题出在 JS 中

你也可能会看到类似这样的内容:

UI 导致的卡顿动画

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

此时,你将拥有一些非常有用的信息来告知你的下一步。

解决 JavaScript 问题

如果你识别出 JS 问题,请在你执行的具体 JS 中寻找线索。在上述场景中,我们看到 RCTEventEmitter 每帧被调用多次。这是上面追踪记录中 JS 线程的放大图:

过多的 JS

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

解决原生 UI 问题

如果你识别出原生 UI 问题,通常有两种情况:

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

太多的 GPU 工作

在第一种场景中,你将看到 UI 线程和/或渲染线程看起来像这样的追踪记录:

过载的 GPU

注意花在 DrawFrame 上的长时间跨过了帧边界。这是等待 GPU 排空上一帧命令缓冲区的时间。

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

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

在 UI 线程上创建新视图

在第二种场景中,你将看到更像这样的内容:

创建视图

注意首先 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 热点性能分析很慢,所以它不会给你准确的测量值。但是,它应该让你了解调用了哪些原生方法,以及每帧期间时间按比例花在哪里。