性能分析
性能分析是分析应用的性能、资源使用情况和行为以识别潜在瓶颈或效率低下的过程。值得利用性能分析工具来确保您的应用在不同设备和条件下都能流畅运行。
对于 iOS,Instruments 是一个非常有价值的工具,而在 Android 上,您应该学习如何使用 systrace
。
但首先,确保开发模式已关闭! 您应该在应用日志中看到 __DEV__ === false, development-level warning are OFF, performance optimizations are ON
。
使用 systrace
分析 Android UI 性能
Android 支持 10k+ 种不同的手机,并且被泛化以支持软件渲染:框架架构和跨许多硬件目标泛化的需求不幸地意味着与 iOS 相比,您获得的免费资源更少。但有时,您可以改进某些方面——而且很多时候根本不是原生代码的错误!
调试此卡顿的第一步是回答每个 16ms 帧期间时间都花在哪里这一基本问题。为此,我们将使用一个名为 systrace
的标准 Android 性能分析工具。
systrace
是一个标准的基于 Android 标记的性能分析工具(安装 Android 平台工具包时会安装)。已分析的代码块被开始/结束标记包围,然后以彩色的图表格式可视化。Android SDK 和 React Native 框架都提供了您可以可视化的标准标记。
1. 收集跟踪
首先,通过 USB 将出现卡顿现象的设备连接到您的计算机,并将其置于您要分析的导航/动画之前的状态。按如下方式运行 systrace
$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>
此命令的快速分解
time
是将收集跟踪的时间长度(以秒为单位)sched
、gfx
和view
是我们关心的 Android SDK 标记(标记集合):sched
提供有关手机每个内核上运行内容的信息,gfx
提供图形信息(例如帧边界),view
提供有关测量、布局和绘制过程的信息-a <your_package_name>
启用特定于应用的标记,特别是内置于 React Native 框架中的标记。your_package_name
可以在应用的AndroidManifest.xml
中找到,格式类似于com.example.app
跟踪开始收集后,执行您关心的动画或交互。在跟踪结束时,systrace 将为您提供一个跟踪链接,您可以在浏览器中打开该链接。
2. 读取跟踪
在浏览器(最好是 Chrome)中打开跟踪后,您应该会看到类似以下内容
使用 WASD 键进行平移和缩放。
如果您的跟踪 .html 文件无法正确打开,请检查浏览器控制台是否有以下内容
由于 Object.observe
在最近的浏览器中已弃用,您可能需要从 Google Chrome 跟踪工具中打开该文件。您可以通过以下方式执行此操作:
- 在 Chrome 中打开标签页 chrome://tracing
- 选择加载
- 选择从上一个命令生成的 html 文件。
选中屏幕右上角的此复选框以突出显示 16ms 帧边界
您应该会看到如上图所示的斑马纹。如果看不到,请尝试在其他设备上进行分析:三星手机在显示 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
等内容来识别它
识别罪魁祸首
流畅的动画应如下所示
每种颜色的变化都是一帧——请记住,为了显示一帧,我们需要在该 16ms 周期结束前完成所有 UI 工作。请注意,没有线程的工作接近帧边界。像这样渲染的应用以 60 FPS 渲染。
但是,如果您注意到卡顿,您可能会看到类似以下内容
请注意,JS 线程几乎一直在执行,并且跨越了帧边界!此应用未以 60 FPS 渲染。在这种情况下,**问题在于 JS**。
您也可能会看到类似以下内容
在这种情况下,UI 和渲染线程是工作跨越帧边界的部分。我们试图在每一帧上渲染的 UI 需要完成过多的工作。在这种情况下,**问题在于正在渲染的原生视图**。
此时,您将获得一些非常有用的信息来指导您的后续步骤。
解决 JavaScript 问题
如果您识别出 JS 问题,请在您正在执行的特定 JS 中查找线索。在上述场景中,我们看到 RCTEventEmitter
每帧被调用多次。以下是上面跟踪中 JS 线程的放大图
这似乎不太对劲。为什么它会被如此频繁地调用?它们实际上是不同的事件吗?这些问题的答案可能取决于您的产品代码。而且很多时候,您会想要查看 shouldComponentUpdate。
解决原生 UI 问题
如果您识别出原生 UI 问题,通常有两种情况
- 您试图在每一帧绘制的 UI 涉及到 GPU 上的过多工作,或者
- 您在动画/交互期间构造新的 UI(例如,在滚动期间加载新内容)。
GPU 工作量过大
在第一种情况下,您将看到一个跟踪,其中 UI 线程和/或渲染线程如下所示
请注意在 DrawFrame
中花费了很长时间,这跨越了帧边界。这是等待 GPU 耗尽上一帧的命令缓冲区的时间。
为了缓解这种情况,您应该
- 调查对复杂、静态内容使用
renderToHardwareTextureAndroid
进行动画/转换(例如Navigator
滑动/alpha 动画) - 确保您**没有**使用
needsOffscreenAlphaCompositing
,该选项默认情况下是禁用的,因为它在大多数情况下会大大增加 GPU 的每帧负载。
在 UI 线程上创建新的视图
在第二种情况下,您会看到更类似以下内容
请注意,首先 JS 线程思考了一会儿,然后您会看到原生模块线程上完成了一些工作,然后是 UI 线程上进行的昂贵的遍历。
除非您能够将创建新 UI 推迟到交互之后,或者能够简化您正在创建的 UI,否则没有快速的方法来缓解这种情况。React Native 团队正在为此提供基础架构级别的解决方案,这将允许在主线程之外创建和配置新的 UI,从而使交互能够平滑地继续。