性能分析
性能分析是分析应用程序的性能、资源使用情况和行为,以识别潜在的瓶颈或效率低下的过程。 值得使用性能分析工具来确保你的应用在不同的设备和条件下都能流畅运行。
对于 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 Thread)、mqt_js
和 mqt_native_modules
。 如果你在 Android 5+ 上运行,我们还关注 Render Thread。
-
UI 线程。 这是标准的 android 测量/布局/绘制发生的地方。 右侧的线程名称将是你的包名(在我的例子中是 book.adsmanager)或 UI Thread。 你在此线程上看到的事件应该看起来像这样,并且与
Choreographer
、traversals
和DispatchUI
有关 -
JS 线程。 这是 JavaScript 代码执行的地方。 线程名称将是
mqt_js
或<...>
,具体取决于你设备上内核的配合程度。 如果它没有名称,要识别它,请查找诸如JSCall
、Bridge.executeJSCall
等内容 -
原生模块线程。 这是执行原生模块调用(例如
UIManager
)的地方。 线程名称将是mqt_native_modules
或<...>
。 如果是后者,要识别它,请查找诸如NativeCall
、callJavaModuleMethod
和onBatchComplete
等内容 -
奖励:Render Thread。 如果你使用的是 Android L (5.0) 及更高版本,你的应用程序中还将有一个渲染线程。 此线程生成用于绘制 UI 的实际 OpenGL 命令。 线程名称将是
RenderThread
或<...>
。 如果是后者,要识别它,请查找诸如DrawFrame
和queueBuffer
等内容
识别问题根源
一个流畅的动画应该看起来像下面这样
每种颜色的变化都是一帧 - 请记住,为了显示一帧,我们所有的 UI 工作都需要在 16 毫秒周期结束前完成。 请注意,没有线程在接近帧边界工作。 像这样渲染的应用程序以 60 FPS 进行渲染。
但是,如果你注意到卡顿,你可能会看到类似这样的情况
请注意,JS 线程几乎一直在执行,并且跨越了帧边界! 此应用程序未以 60 FPS 渲染。 在这种情况下,问题在于 JS。
你也可能会看到类似这样的情况
在这种情况下,UI 线程和渲染线程是工作跨越帧边界的线程。 我们尝试在每帧上渲染的 UI 需要完成太多的工作。 在这种情况下,问题在于正在渲染的原生视图。
此时,你将获得一些非常有用的信息来指导你的下一步操作。
解决 JavaScript 问题
如果你确定了 JS 问题,请在你正在执行的特定 JS 代码中寻找线索。 在上面的场景中,我们看到 RCTEventEmitter
每帧被调用多次。 这是上面跟踪信息中 JS 线程的放大视图
这似乎不太对劲。 为什么它被调用如此频繁? 它们实际上是不同的事件吗? 这些问题的答案可能取决于你的产品代码。 很多时候,你会想研究 shouldComponentUpdate。
解决原生 UI 问题
如果你确定了原生 UI 问题,通常有两种情况
- 你尝试每帧绘制的 UI 在 GPU 上涉及过多的工作,或者
- 你在动画/交互期间构建新的 UI(例如,在滚动期间加载新内容)。
GPU 工作过多
在第一种情况下,你将看到一个跟踪信息,其中 UI 线程和/或 Render Thread 看起来像这样
请注意,在跨越帧边界的 DrawFrame
中花费了很长时间。 这是等待 GPU 从上一帧耗尽其命令缓冲区所花费的时间。
为了缓解这种情况,你应该
- 考虑对正在动画/转换的复杂静态内容使用
renderToHardwareTextureAndroid
(例如Navigator
的滑动/alpha 动画) - 确保你没有使用
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 热点分析速度较慢,因此它不会为你提供准确的测量结果。 但是,它应该让你了解正在调用哪些原生方法,以及在每帧期间时间按比例花费在哪里。