性能分析
性能分析是分析应用程序性能、资源使用和行为以识别潜在瓶颈或效率低下的过程。利用性能分析工具可以确保您的应用程序在不同设备和条件下平稳运行。
对于 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 中打开跟踪后,您应该会看到类似这样的内容
使用 WASD 键平移和缩放。
确切的 UI 可能有所不同,但下面的说明将适用于您使用的任何工具。
选中屏幕右上角的此复选框以突出显示 16 毫秒的帧边界
您应该会看到像上面截图中的斑马纹。如果没有,请尝试在不同的设备上进行性能分析:众所周知,三星在显示 vsync 时存在问题,而 Nexus 系列通常非常可靠。
3. 查找您的进程
滚动直到您看到您的包名(或部分包名)。在本例中,我正在分析 com.facebook.adsmanager
,由于内核中愚蠢的线程名称限制,它显示为 book.adsmanager
。
在左侧,您将看到一组线程,它们对应于右侧的时间线行。为了我们的目的,我们关心几个线程:UI 线程(具有您的包名或 UI 线程名称)、mqt_js
和 mqt_native_modules
。如果您在 Android 5+ 上运行,我们还关心渲染线程。
-
UI 线程。这是标准 Android 测量/布局/绘制发生的地方。右侧的线程名称将是您的包名(我的情况是 book.adsmanager)或 UI 线程。您在此线程上看到的事件应类似于此,并且与
Choreographer
、traversals
和DispatchUI
有关。 -
JS 线程。这是 JavaScript 执行的地方。线程名称将是
mqt_js
或<...>
,具体取决于您设备上的内核的配合程度。如果它没有名称,要识别它,请查找诸如JSCall
、Bridge.executeJSCall
等内容。 -
原生模块线程。这是原生模块调用(例如
UIManager
)执行的地方。线程名称将是mqt_native_modules
或<...>
。在后一种情况下,要识别它,请查找诸如NativeCall
、callJavaModuleMethod
和onBatchComplete
等内容。 -
额外:渲染线程。如果您使用 Android L (5.0) 及更高版本,您的应用程序中还将有一个渲染线程。此线程生成用于绘制 UI 的实际 OpenGL 命令。线程名称将是
RenderThread
或<...>
。在后一种情况下,要识别它,请查找诸如DrawFrame
和queueBuffer
等内容。
识别罪魁祸首
流畅的动画应该像这样
颜色变化代表一个帧——请记住,为了显示一个帧,我们所有的 UI 工作都需要在该 16ms 周期结束时完成。请注意,没有任何线程在接近帧边界的地方工作。像这样渲染的应用程序以 60 FPS 的速度渲染。
但是,如果您注意到卡顿,您可能会看到类似这样的情况
请注意,JS 线程几乎一直在执行,并且跨越帧边界!此应用程序未以 60 FPS 渲染。在这种情况下,问题出在 JS 中。
您可能还会看到类似这样的情况
在这种情况下,UI 和渲染线程是工作跨越帧边界的线程。我们试图在每个帧上渲染的 UI 需要完成太多工作。在这种情况下,问题在于正在渲染的原生视图。
此时,您将获得一些非常有用的信息来指导您的下一步行动。
解决 JavaScript 问题
如果您发现 JS 问题,请在您正在执行的特定 JS 中寻找线索。在上面的场景中,我们看到 RCTEventEmitter
在每个帧中被多次调用。这是上面跟踪中 JS 线程的放大图
这看起来不对劲。为什么它被调用得如此频繁?它们真的是不同的事件吗?这些问题的答案可能取决于您的产品代码。很多时候,您会希望查看 shouldComponentUpdate。
解决原生 UI 问题
如果您发现原生 UI 问题,通常有两种情况
- 您尝试在每一帧绘制的 UI 涉及 GPU 上过多的工作,或者
- 您正在动画/交互过程中构建新的 UI(例如在滚动过程中加载新内容)。
GPU 工作过多
在第一种情况下,您将看到 UI 线程和/或渲染线程看起来像这样
请注意在 DrawFrame
中花费了很长时间,这会跨越帧边界。这是等待 GPU 排空前一帧命令缓冲区的时间。
为了缓解这种情况,您应该
- 调查对正在动画/变换的复杂、静态内容(例如
Navigator
滑动/alpha 动画)使用renderToHardwareTextureAndroid
- 确保您未使用
needsOffscreenAlphaCompositing
,它默认是禁用的,因为它在大多数情况下会大大增加 GPU 每帧的负载。
在 UI 线程上创建新视图
在第二种情况中,您会看到更多类似这样的情况
请注意,首先 JS 线程思考了一会儿,然后您会看到在原生模块线程上完成了一些工作,接着是在 UI 线程上进行的昂贵遍历。
除非您能够将新 UI 的创建推迟到交互之后,或者您能够简化您正在创建的 UI,否则没有快速的方法可以缓解这种情况。React Native 团队正在为此开发基础设施级别的解决方案,该解决方案将允许在新 UI 脱离主线程创建和配置,从而使交互能够平稳地进行。
查找原生 CPU 热点
如果问题似乎出在原生端,您可以使用CPU 热点分析器来获取更多详细信息。打开 Android Studio Profiler 面板并选择“查找 CPU 热点(Java/Kotlin 方法记录)”。
请确保您选择“查找 CPU 热点(Java/Kotlin 录制)”而不是“查找 CPU 热点(调用栈采样)”。它们有相似的图标但功能不同。
执行交互并按下“停止录制”。录制是资源密集型的,因此请保持交互简短。然后,您可以选择在 Android Studio 中检查生成的跟踪,或导出并在Firefox Profiler等在线工具中打开它。
与系统跟踪不同,CPU 热点分析速度较慢,因此无法为您提供准确的测量结果。但是,它应该能让您了解正在调用哪些原生方法,以及在每个帧中按比例花费时间的位置。