性能分析
性能分析是分析应用程序的性能、资源使用情况和行为以识别潜在瓶颈或效率低下的过程。利用性能分析工具可以确保您的应用程序在不同设备和条件下平稳运行。
对于 iOS,Instruments 是一个非常宝贵的工具,而在 Android 上,您应该学习使用Android Studio Profiler。
但首先,请确保开发模式已关闭!。
使用系统跟踪分析 Android UI 性能
Android 支持 10k+ 不同的手机,并普遍支持软件渲染:框架架构和需要泛化到许多硬件目标,不幸的是,这意味着您免费获得的东西比 iOS 少。但有时,有些事情您可以改进——很多时候根本不是原生代码的错!
调试此卡顿的第一步是回答在每个 16 毫秒帧期间您的时间花在哪里这个基本问题。为此,我们将使用 Android Studio 中内置的系统跟踪分析器。
1. 收集跟踪
首先,通过 USB 将出现您想要调查的卡顿的设备连接到您的计算机。在 Android Studio 中打开您项目的 android 文件夹,在右上角窗格中选择您的设备,然后将您的项目作为可分析的运行。
当您的应用程序作为可分析的构建并正在设备上运行时,将您的应用程序带到您想要分析的导航/动画之前的点,并在 Android Studio Profiler 窗格中启动“捕获系统活动”任务。
一旦跟踪开始收集,执行您关心的动画或交互。然后按下“停止录制”。您现在可以直接在 Android Studio 中检查跟踪。或者,您可以在“过去录制”窗格中选择它,按下“导出录制”,然后在 Perfetto 等工具中打开它。
2. 读取跟踪
在 Android Studio 或 Perfetto 中打开跟踪后,您应该会看到类似以下内容

使用 WASD 键平移和缩放。
确切的用户界面可能有所不同,但下面的说明适用于您使用的任何工具。
勾选屏幕右上角的此复选框以突出显示 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 工作都需要在该 16 毫秒周期结束之前完成。请注意,没有线程接近帧边界工作。像这样渲染的应用程序以 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 热点分析很慢,因此它不会为您提供准确的测量结果。但是,它应该能让您了解正在调用哪些原生方法,以及每个帧中时间按比例花费在哪里。