分析
分析是分析应用的性能、资源使用情况和行为以识别潜在瓶颈或低效率的过程。值得使用分析工具来确保你的应用在不同设备和条件下顺利运行。
¥Profiling is the process of analyzing an app's performance, resource usage, and behavior to identify potential bottlenecks or inefficiencies. It's worth making use of profiling tools to ensure your app works smoothly across different devices and conditions.
对于 iOS,Instruments 是一个非常宝贵的工具,而在 Android 上,你应该学习使用 systrace
。
¥For iOS, Instruments is an invaluable tool, and on Android you should learn to use systrace
.
但首先,确保开发模式关闭! 你应该在应用日志中看到 __DEV__ === false, development-level warning are OFF, performance optimizations are ON
。
¥But first, make sure that Development Mode is OFF! You should see __DEV__ === false, development-level warning are OFF, performance optimizations are ON
in your application logs.
使用 systrace
分析 Android UI 性能
¥Profiling Android UI Performance with systrace
Android 支持 10k+ 不同的手机,并普遍支持软件渲染:不幸的是,框架架构和需要跨许多硬件目标进行推广意味着相对于 iOS,你免费获得的资源更少。但有时,有些事情是可以改进的 - 而且很多时候这根本不是原生代码的错!
¥Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve -- and many times it's not native code's fault at all!
调试此卡顿的第一步是回答每个 16 毫秒帧期间你的时间都花在哪里的基本问题。为此,我们将使用名为 systrace
的标准 Android 分析工具。
¥The first step for debugging this jank is to answer the fundamental question of where your time is being spent during each 16ms frame. For that, we'll be using a standard Android profiling tool called systrace
.
systrace
是一个标准的 Android 基于标记的分析工具(在安装 Android 平台工具包时安装)。分析的代码块被开始/结束标记包围,然后以彩色图表格式可视化。Android SDK 和 React Native 框架都提供了可可视化的标准标记。
¥systrace
is a standard Android marker-based profiling tool (and is installed when you install the Android platform-tools package). Profiled code blocks are surrounded by start/end markers which are then visualized in a colorful chart format. Both the Android SDK and React Native framework provide standard markers that you can visualize.
1. 收集踪迹
¥ Collecting a trace
首先,通过 USB 将显示你想要调查的卡顿的设备连接到你的计算机,并将其连接到你想要分析的导航/动画之前的位置。按如下方式运行 systrace
:
¥First, connect a device that exhibits the stuttering you want to investigate to your computer via USB and get it to the point right before the navigation/animation you want to profile. Run systrace
as follows:
$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>
该命令的快速分解:
¥A quick breakdown of this command:
-
time
是收集跟踪的时间长度(以秒为单位)¥
time
is the length of time the trace will be collected in seconds -
sched
、gfx
、view
是我们关心的 android SDK 标签(标记的集合):sched
为你提供有关手机每个核心上运行的信息,gfx
为你提供帧边界等图形信息,view
为你提供有关测量、布局和绘制通道的信息¥
sched
,gfx
, andview
are the android SDK tags (collections of markers) we care about:sched
gives you information about what's running on each core of your phone,gfx
gives you graphics info such as frame boundaries, andview
gives you information about measure, layout, and draw passes -
-a <your_package_name>
启用特定于应用的标记,特别是内置于 React Native 框架中的标记。your_package_name
可以在你应用的AndroidManifest.xml
中找到,看起来像com.example.app
¥
-a <your_package_name>
enables app-specific markers, specifically the ones built into the React Native framework.your_package_name
can be found in theAndroidManifest.xml
of your app and looks likecom.example.app
一旦跟踪开始收集,就执行你关心的动画或交互。在跟踪结束时,systrace 将为你提供一个指向跟踪的链接,你可以在浏览器中打开该链接。
¥Once the trace starts collecting, perform the animation or interaction you care about. At the end of the trace, systrace will give you a link to the trace which you can open in your browser.
2. 读取踪迹
¥ Reading the trace
在浏览器(最好是 Chrome)中打开跟踪后,你应该看到如下内容:
¥After opening the trace in your browser (preferably Chrome), you should see something like this:
使用 WASD 键进行平移和缩放。
¥Use the WASD keys to strafe and zoom.
如果你的跟踪 .html 文件无法正确打开,请检查浏览器控制台是否存在以下情况:
¥If your trace .html file isn't opening correctly, check your browser console for the following:
由于 Object.observe
在最新的浏览器中已被弃用,因此你可能必须从 Google Chrome 跟踪工具打开该文件。你可以通过以下方式执行此操作:
¥Since Object.observe
was deprecated in recent browsers, you may have to open the file from the Google Chrome Tracing tool. You can do so by:
-
Opening tab in chrome chrome://tracing
-
选择负载
¥Selecting load
-
选择从上一个命令生成的 html 文件。
¥Selecting the html file generated from the previous command.
选中屏幕右上角的此复选框以高亮 16ms 帧边界:
¥Check this checkbox at the top right of the screen to highlight the 16ms frame boundaries:
你应该看到斑马条纹,如上面的屏幕截图所示。如果不这样做,请尝试在其他设备上进行分析:众所周知,三星在显示垂直同步方面存在问题,而 Nexus 系列通常相当可靠。
¥You should see zebra stripes as in the screenshot above. If you don't, try profiling on a different device: Samsung has been known to have issues displaying vsyncs while the Nexus series is generally pretty reliable.
3. 找到你的进程
¥ Find your process
滚动直到看到封装名称(部分)。在本例中,我正在分析 com.facebook.adsmanager
,由于内核中愚蠢的线程名称限制,它显示为 book.adsmanager
。
¥Scroll until you see (part of) the name of your package. In this case, I was profiling com.facebook.adsmanager
, which shows up as book.adsmanager
because of silly thread name limits in the kernel.
在左侧,你将看到一组与右侧时间线行相对应的线程。为了我们的目的,我们关心一些线程:UI 线程(具有你的包名称或名称 UI 线程)、mqt_js
和 mqt_native_modules
。如果你在 Android 5+ 上运行,我们还关心渲染线程。
¥On the left side, you'll see a set of threads which correspond to the timeline rows on the right. There are a few threads we care about for our purposes: the UI thread (which has your package name or the name UI Thread), mqt_js
, and mqt_native_modules
. If you're running on Android 5+, we also care about the Render Thread.
-
用户界面线程。这是标准 android 测量/布局/绘制发生的地方。右侧的线程名称将是你的包名称(在我的案例中为 book.adsmanager)或 UI 线程。你在此线程上看到的事件应类似于以下内容,并且与
Choreographer
、traversals
和DispatchUI
有关:¥UI Thread. This is where standard android measure/layout/draw happens. The thread name on the right will be your package name (in my case book.adsmanager) or UI Thread. The events that you see on this thread should look something like this and have to do with
Choreographer
,traversals
, andDispatchUI
:
-
JS 线程。这是 JavaScript 执行的地方。线程名称将为
mqt_js
或<...>
,具体取决于设备上内核的协作程度。如果它没有名称,要识别它,请查找诸如JSCall
、Bridge.executeJSCall
等的内容:¥JS Thread. This is where JavaScript is executed. The thread name will be either
mqt_js
or<...>
depending on how cooperative the kernel on your device is being. To identify it if it doesn't have a name, look for things likeJSCall
,Bridge.executeJSCall
, etc:
-
原生模块线程。这是执行原生模块调用(例如
UIManager
)的地方。线程名称将为mqt_native_modules
或<...>
。要在后一种情况下识别它,请查找诸如NativeCall
、callJavaModuleMethod
和onBatchComplete
之类的内容:¥Native Modules Thread. This is where native module calls (e.g. the
UIManager
) are executed. The thread name will be eithermqt_native_modules
or<...>
. To identify it in the latter case, look for things likeNativeCall
,callJavaModuleMethod
, andonBatchComplete
:
-
奖金:渲染线程。如果你使用的是 Android L (5.0) 及更高版本,你的应用中还将有一个渲染线程。该线程生成用于绘制 UI 的实际 OpenGL 命令。线程名称将为
RenderThread
或<...>
。要在后一种情况下识别它,请查找诸如DrawFrame
和queueBuffer
之类的内容:¥Bonus: Render Thread. If you're using Android L (5.0) and up, you will also have a render thread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either
RenderThread
or<...>
. To identify it in the latter case, look for things likeDrawFrame
andqueueBuffer
:
找出罪魁祸首
¥Identifying a culprit
流畅的动画应该如下所示:
¥A smooth animation should look something like the following:
每次颜色变化都是一帧 - 请记住,为了显示一帧,我们所有的 UI 工作都需要在 16 毫秒周期结束时完成。请注意,没有线程在靠近帧边界的地方工作。像这样的应用渲染速度为 60 FPS。
¥Each change in color is a frame -- remember that in order to display a frame, all our UI work needs to be done by the end of that 16ms period. Notice that no thread is working close to the frame boundary. An application rendering like this is rendering at 60 FPS.
但是,如果你注意到了 chop,你可能会看到类似这样的内容:
¥If you noticed chop, however, you might see something like this:
请注意,JS 线程几乎一直在执行,并且跨帧边界!此应用不以 60 FPS 渲染。这样的话,问题就出在 JS 上。
¥Notice that the JS thread is executing almost all the time, and across frame boundaries! This app is not rendering at 60 FPS. In this case, the problem lies in JS.
你可能还会看到类似这样的内容:
¥You might also see something like this:
在这种情况下,UI 和渲染线程是跨帧边界工作的线程。我们尝试在每一帧上渲染的 UI 需要完成太多工作。在这种情况下,问题在于正在渲染的原生视图。
¥In this case, the UI and render threads are the ones that have work crossing frame boundaries. The UI that we're trying to render on each frame is requiring too much work to be done. In this case, the problem lies in the native views being rendered.
此时,你将获得一些非常有用的信息来指导你的后续步骤。
¥At this point, you'll have some very helpful information to inform your next steps.
解决 JavaScript 问题
¥Resolving JavaScript issues
如果你发现了 JS 问题,请在你正在执行的特定 JS 中寻找线索。在上面的场景中,我们看到 RCTEventEmitter
每帧被调用多次。下面是上面跟踪中 JS 线程的放大图:
¥If you identified a JS problem, look for clues in the specific JS that you're executing. In the scenario above, we see RCTEventEmitter
being called multiple times per frame. Here's a zoom-in of the JS thread from the trace above:
这似乎不对。为什么它被如此频繁地调用?它们实际上是不同的事件吗?这些问题的答案可能取决于你的产品代码。很多时候,你会想要研究 shouldComponentUpdate。
¥This doesn't seem right. Why is it being called so often? Are they actually different events? The answers to these questions will probably depend on your product code. And many times, you'll want to look into shouldComponentUpdate.
解决原生 UI 问题
¥Resolving native UI Issues
如果你发现原生 UI 问题,通常有两种情况:
¥If you identified a native UI problem, there are usually two scenarios:
-
你尝试绘制每一帧的 UI 涉及 GPU 的太多工作,或者
¥the UI you're trying to draw each frame involves too much work on the GPU, or
-
你在动画/交互期间构建新的 UI(例如,在滚动期间加载新内容)。
¥You're constructing new UI during the animation/interaction (e.g. loading in new content during a scroll).
GPU 工作过多
¥Too much GPU work
在第一个场景中,你将看到具有 UI 线程和/或渲染线程的跟踪,如下所示:
¥In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this:
请注意 DrawFrame
中花费的大量时间跨越了帧边界。这是等待 GPU 耗尽前一帧的命令缓冲区所花费的时间。
¥Notice the long amount of time spent in DrawFrame
that crosses frame boundaries. This is time spent waiting for the GPU to drain its command buffer from the previous frame.
为了缓解这种情况,你应该:
¥To mitigate this, you should:
-
研究使用
renderToHardwareTextureAndroid
来处理正在动画/转换的复杂静态内容(例如Navigator
幻灯片/Alpha 动画)¥investigate using
renderToHardwareTextureAndroid
for complex, static content that is being animated/transformed (e.g. theNavigator
slide/alpha animations) -
确保你没有使用
needsOffscreenAlphaCompositing
,该选项默认处于禁用状态,因为在大多数情况下它会大大增加 GPU 上的每帧负载。¥make sure that you are not using
needsOffscreenAlphaCompositing
, which is disabled by default, as it greatly increases the per-frame load on the GPU in most cases.
在 UI 线程上创建新视图
¥Creating new views on the UI thread
在第二种情况下,你会看到类似这样的内容:
¥In the second scenario, you'll see something more like this:
请注意,首先 JS 线程会思考一下,然后你会看到原生模块线程上完成了一些工作,然后是 UI 线程上昂贵的遍历。
¥Notice that first the JS thread thinks for a bit, then you see some work done on the native modules thread, followed by an expensive traversal on the UI thread.
没有快速的方法可以缓解这种情况,除非你能够将创建新 UI 推迟到交互之后,或者你能够简化正在创建的 UI。React Native 团队正在为此开发基础设施级别的解决方案,该解决方案将允许在主线程之外创建和配置新的 UI,从而使交互能够顺利地继续。
¥There isn't a quick way to mitigate this unless you're able to postpone creating new UI until after the interaction, or you are able to simplify the UI you're creating. The react native team is working on an infrastructure level solution for this that will allow new UI to be created and configured off the main thread, allowing the interaction to continue smoothly.