Merge Android UI Performance into Performance doc, reorder sidebar
Summary: Doing some cleanup in preparation for CRNA. Recommend `FlatList` and React Navigation for perf. Tag docs that may only apply to apps ejected from CRNA. Currently has no effect. Closes https://github.com/facebook/react-native/pull/12692 Differential Revision: D4654077 Pulled By: hramos fbshipit-source-id: 1245d80d66e37d9dca9e9daf23e8b93c65cd1bf7
This commit is contained in:
parent
c77f09b174
commit
c503dae446
|
@ -5,7 +5,7 @@ layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/accessibility.html
|
permalink: docs/accessibility.html
|
||||||
next: timers
|
next: timers
|
||||||
previous: animations
|
previous: debugging
|
||||||
---
|
---
|
||||||
|
|
||||||
## Native App Accessibility (iOS and Android)
|
## Native App Accessibility (iOS and Android)
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Building React Native from source
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (Android)
|
category: Guides (Android)
|
||||||
permalink: docs/android-building-from-source.html
|
permalink: docs/android-building-from-source.html
|
||||||
|
banner: ejected
|
||||||
next: activityindicator
|
next: activityindicator
|
||||||
previous: android-ui-performance
|
previous: android-ui-performance
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,165 +1,7 @@
|
||||||
---
|
---
|
||||||
id: android-ui-performance
|
id: android-ui-performance
|
||||||
title: Profiling Android UI Performance
|
title: Profiling Android UI Performance
|
||||||
layout: docs
|
layout: redirect
|
||||||
category: Guides (Android)
|
|
||||||
permalink: docs/android-ui-performance.html
|
permalink: docs/android-ui-performance.html
|
||||||
next: android-building-from-source
|
destinationUrl: performance.html
|
||||||
previous: signed-apk-android
|
|
||||||
---
|
---
|
||||||
|
|
||||||
We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, 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!).
|
|
||||||
|
|
||||||
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. But first...
|
|
||||||
|
|
||||||
> Make sure that JS dev mode is OFF!
|
|
||||||
>
|
|
||||||
> You should see `__DEV__ === false, development-level warning are OFF, performance optimizations are ON` in your application logs (which you can view using `adb logcat`)
|
|
||||||
|
|
||||||
## Profiling with Systrace
|
|
||||||
|
|
||||||
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 markers 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.
|
|
||||||
|
|
||||||
### Collecting a trace
|
|
||||||
|
|
||||||
> NOTE:
|
|
||||||
>
|
|
||||||
> Systrace support was added in react-native `v0.15`. You will need to build with that version to collect a trace.
|
|
||||||
|
|
||||||
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` is the length of time the trace will be collected in seconds
|
|
||||||
- `sched`, `gfx`, and `view` 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, and `view` gives you information about measure, layout, and draw passes
|
|
||||||
- `-a <your_package_name>` enables app-specific markers, specifically the ones built into the React Native framework. `your_package_name` can be found in the `AndroidManifest.xml` of your app and looks like `com.example.app`
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Reading the trace
|
|
||||||
|
|
||||||
After opening the trace in your browser (preferably Chrome), you should see something like this:
|
|
||||||
|
|
||||||
![Example](img/SystraceExample.png)
|
|
||||||
|
|
||||||
If your trace .html file isn't opening correctly, check your browser console for the following:
|
|
||||||
|
|
||||||
![ObjectObserveError](img/ObjectObserveError.png)
|
|
||||||
|
|
||||||
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
|
|
||||||
- Selecting the html file generated from the previous command.
|
|
||||||
|
|
||||||
**HINT**: Use the WASD keys to strafe and zoom
|
|
||||||
|
|
||||||
### Enable VSync highlighting
|
|
||||||
|
|
||||||
The first thing you should do is highlight the 16ms frame boundaries if you haven't already done that. Check this checkbox at the top right of the screen:
|
|
||||||
|
|
||||||
![Enable VSync Highlighting](img/SystraceHighlightVSync.png)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Find your process
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
On the left side, you'll see a set of threads which correspond to the timeline rows on the right. There are three/four 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.
|
|
||||||
|
|
||||||
### 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`, and `DispatchUI`:
|
|
||||||
|
|
||||||
![UI Thread Example](img/SystraceUIThreadExample.png)
|
|
||||||
|
|
||||||
### JS Thread
|
|
||||||
|
|
||||||
This is where JS 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 like `JSCall`, `Bridge.executeJSCall`, etc:
|
|
||||||
|
|
||||||
![JS Thread Example](img/SystraceJSThreadExample.png)
|
|
||||||
|
|
||||||
### Native Modules Thread
|
|
||||||
|
|
||||||
This is where native module calls (e.g. the `UIManager`) are executed. The thread name will be either `mqt_native_modules` or `<...>`. To identify it in the latter case, look for things like `NativeCall`, `callJavaModuleMethod`, and `onBatchComplete`:
|
|
||||||
|
|
||||||
![Native Modules Thread Example](img/SystraceNativeModulesThreadExample.png)
|
|
||||||
|
|
||||||
### 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 like `DrawFrame` and `queueBuffer`:
|
|
||||||
|
|
||||||
![Render Thread Example](img/SystraceRenderThreadExample.png)
|
|
||||||
|
|
||||||
## Identifying a culprit
|
|
||||||
|
|
||||||
A smooth animation should look something like the following:
|
|
||||||
|
|
||||||
![Smooth Animation](img/SystraceWellBehaved.png)
|
|
||||||
|
|
||||||
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 60FPS.
|
|
||||||
|
|
||||||
If you noticed chop, however, you might see something like this:
|
|
||||||
|
|
||||||
![Choppy Animation from JS](img/SystraceBadJS.png)
|
|
||||||
|
|
||||||
Notice that the JS thread is executing basically all the time, and across frame boundaries! This app is not rendering at 60FPS. In this case, **the problem lies in JS**.
|
|
||||||
|
|
||||||
You might also see something like this:
|
|
||||||
|
|
||||||
![Choppy Animation from UI](img/SystraceBadUI.png)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## JS Issues
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
![Too much JS](img/SystraceBadJS2.png)
|
|
||||||
|
|
||||||
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](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate).
|
|
||||||
|
|
||||||
> **TODO**: Add more tools for profiling JS
|
|
||||||
|
|
||||||
## Native UI Issues
|
|
||||||
|
|
||||||
If you identified a native UI problem, there are usually two scenarios:
|
|
||||||
|
|
||||||
1. the UI you're trying to draw each frame involves to much work on the GPU, or
|
|
||||||
2. You're constructing new UI during the animation/interaction (e.g. loading in new content during a scroll).
|
|
||||||
|
|
||||||
### Too much GPU work
|
|
||||||
|
|
||||||
In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this:
|
|
||||||
|
|
||||||
![Overloaded GPU](img/SystraceBadUI.png)
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
- investigate using `renderToHardwareTextureAndroid` for complex, static content that is being animated/transformed (e.g. the `Navigator` slide/alpha animations)
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
If these don't help and you want to dig deeper into what the GPU is actually doing, you can check out [Tracer for OpenGL ES](http://developer.android.com/tools/help/gltracer.html).
|
|
||||||
|
|
||||||
### Creating new views on the UI thread
|
|
||||||
|
|
||||||
In the second scenario, you'll see something more like this:
|
|
||||||
|
|
||||||
![Creating Views](img/SystraceBadCreateUI.png)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
There isn't an easy 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 a 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.
|
|
||||||
|
|
||||||
## Still stuck?
|
|
||||||
|
|
||||||
If you are confused or stuck, please post ask on [Stack Overflow with the react-native tag](http://stackoverflow.com/tags/react-native). If you are unable to get a response there, or find an issue with a core component, please [File a Github issue](https://github.com/facebook/react-native/issues).
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ title: Animations
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/animations.html
|
permalink: docs/animations.html
|
||||||
next: accessibility
|
next: navigation
|
||||||
previous: handling-touches
|
previous: handling-touches
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Colors
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/colors.html
|
permalink: docs/colors.html
|
||||||
next: images
|
next: platform-specific-code
|
||||||
previous: integration-with-existing-apps
|
previous: images
|
||||||
---
|
---
|
||||||
|
|
||||||
The following formats are supported:
|
The following formats are supported:
|
||||||
|
|
|
@ -4,8 +4,9 @@ title: Communication between native and React Native
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (iOS)
|
category: Guides (iOS)
|
||||||
permalink: docs/communication-ios.html
|
permalink: docs/communication-ios.html
|
||||||
|
banner: ejected
|
||||||
next: native-modules-android
|
next: native-modules-android
|
||||||
previous: running-on-simulator-ios
|
previous: linking-libraries-ios
|
||||||
---
|
---
|
||||||
|
|
||||||
In [Integrating with Existing Apps guide](docs/integration-with-existing-apps.html) and [Native UI Components guide](docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques.
|
In [Integrating with Existing Apps guide](docs/integration-with-existing-apps.html) and [Native UI Components guide](docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques.
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Debugging
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/debugging.html
|
permalink: docs/debugging.html
|
||||||
next: testing
|
next: accessibility
|
||||||
previous: direct-manipulation
|
previous: platform-specific-code
|
||||||
---
|
---
|
||||||
|
|
||||||
## Accessing the In-App Developer Menu
|
## Accessing the In-App Developer Menu
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Direct Manipulation
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/direct-manipulation.html
|
permalink: docs/direct-manipulation.html
|
||||||
next: debugging
|
next: performance
|
||||||
previous: timers
|
previous: javascript-environment
|
||||||
---
|
---
|
||||||
|
|
||||||
It is sometimes necessary to make changes directly to a component
|
It is sometimes necessary to make changes directly to a component
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Gesture Responder System
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/gesture-responder-system.html
|
permalink: docs/gesture-responder-system.html
|
||||||
next: native-modules-ios
|
next: testing
|
||||||
previous: platform-specific-code
|
previous: performance
|
||||||
---
|
---
|
||||||
|
|
||||||
The gesture responder system manages the lifecycle of gestures in your app. A touch can go through several phases as the app determines what the user's intention is. For example, the app needs to determine if the touch is scrolling, sliding on a widget, or tapping. This can even change during the duration of a touch. There can also be multiple simultaneous touches.
|
The gesture responder system manages the lifecycle of gestures in your app. A touch can go through several phases as the app determines what the user's intention is. For example, the app needs to determine if the touch is scrolling, sliding on a widget, or tapping. This can even change during the duration of a touch. There can also be multiple simultaneous touches.
|
||||||
|
|
|
@ -5,7 +5,7 @@ layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/handling-touches.html
|
permalink: docs/handling-touches.html
|
||||||
next: animations
|
next: animations
|
||||||
previous: images
|
previous: more-resources
|
||||||
---
|
---
|
||||||
|
|
||||||
Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map.
|
Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map.
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Headless JS
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (Android)
|
category: Guides (Android)
|
||||||
permalink: docs/headless-js-android.html
|
permalink: docs/headless-js-android.html
|
||||||
|
banner: ejected
|
||||||
next: signed-apk-android
|
next: signed-apk-android
|
||||||
previous: native-components-android
|
previous: native-components-android
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Images
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/images.html
|
permalink: docs/images.html
|
||||||
next: handling-touches
|
next: colors
|
||||||
previous: colors
|
previous: navigation
|
||||||
---
|
---
|
||||||
|
|
||||||
## Static Image Resources
|
## Static Image Resources
|
||||||
|
|
|
@ -4,8 +4,9 @@ title: Integration With Existing Apps
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/integration-with-existing-apps.html
|
permalink: docs/integration-with-existing-apps.html
|
||||||
next: colors
|
banner: ejected
|
||||||
previous: more-resources
|
next: running-on-device
|
||||||
|
previous: testing
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="integration-toggler">
|
<div class="integration-toggler">
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: JavaScript Environment
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/javascript-environment.html
|
permalink: docs/javascript-environment.html
|
||||||
next: navigation
|
next: direct-manipulation
|
||||||
previous: testing
|
previous: timers
|
||||||
---
|
---
|
||||||
|
|
||||||
## JavaScript Runtime
|
## JavaScript Runtime
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Linking Libraries
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (iOS)
|
category: Guides (iOS)
|
||||||
permalink: docs/linking-libraries-ios.html
|
permalink: docs/linking-libraries-ios.html
|
||||||
|
banner: ejected
|
||||||
next: running-on-simulator-ios
|
next: running-on-simulator-ios
|
||||||
previous: native-components-ios
|
previous: native-components-ios
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,7 +4,7 @@ title: More Resources
|
||||||
layout: docs
|
layout: docs
|
||||||
category: The Basics
|
category: The Basics
|
||||||
permalink: docs/more-resources.html
|
permalink: docs/more-resources.html
|
||||||
next: integration-with-existing-apps
|
next: handling-touches
|
||||||
previous: networking
|
previous: networking
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Native UI Components
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (Android)
|
category: Guides (Android)
|
||||||
permalink: docs/native-components-android.html
|
permalink: docs/native-components-android.html
|
||||||
|
banner: ejected
|
||||||
next: headless-js-android
|
next: headless-js-android
|
||||||
previous: native-modules-android
|
previous: native-modules-android
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Native UI Components
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (iOS)
|
category: Guides (iOS)
|
||||||
permalink: docs/native-components-ios.html
|
permalink: docs/native-components-ios.html
|
||||||
|
banner: ejected
|
||||||
next: linking-libraries-ios
|
next: linking-libraries-ios
|
||||||
previous: native-modules-ios
|
previous: native-modules-ios
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Native Modules
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (Android)
|
category: Guides (Android)
|
||||||
permalink: docs/native-modules-android.html
|
permalink: docs/native-modules-android.html
|
||||||
|
banner: ejected
|
||||||
next: native-components-android
|
next: native-components-android
|
||||||
previous: communication-ios
|
previous: communication-ios
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,8 +4,9 @@ title: Native Modules
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (iOS)
|
category: Guides (iOS)
|
||||||
permalink: docs/native-modules-ios.html
|
permalink: docs/native-modules-ios.html
|
||||||
|
banner: ejected
|
||||||
next: native-components-ios
|
next: native-components-ios
|
||||||
previous: gesture-responder-system
|
previous: upgrading
|
||||||
---
|
---
|
||||||
|
|
||||||
Sometimes an app needs access to platform API, and React Native doesn't have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.
|
Sometimes an app needs access to platform API, and React Native doesn't have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Navigation
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/navigation.html
|
permalink: docs/navigation.html
|
||||||
next: performance
|
next: images
|
||||||
previous: javascript-environment
|
previous: animations
|
||||||
---
|
---
|
||||||
|
|
||||||
This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use React Navigation.
|
This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use React Navigation.
|
||||||
|
|
|
@ -4,295 +4,125 @@ title: Performance
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/performance.html
|
permalink: docs/performance.html
|
||||||
next: understanding-cli
|
next: gesture-responder-system
|
||||||
previous: navigation
|
previous: direct-manipulation
|
||||||
---
|
---
|
||||||
|
|
||||||
A compelling reason for using React Native instead of WebView-based
|
A compelling reason for using React Native instead of WebView-based tools is to achieve 60 frames per second and a native look and feel to your apps.
|
||||||
tools is to achieve 60 FPS and a native look & feel to your apps. Where
|
Where possible, we would like for React Native to do the right thing and help you to focus on your app instead of performance optimization,
|
||||||
possible, we would like for React Native to do the right thing and help
|
but there are areas where we're not quite there yet,
|
||||||
you to focus on your app instead of performance optimization, but there
|
and others where React Native (similar to writing native code directly) cannot possibly determine the best way to optimize for you and so manual intervention will be necessary.
|
||||||
are areas where we're not quite there yet, and others where React Native
|
We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible.
|
||||||
(similar to writing native code directly) cannot possibly determine the
|
|
||||||
best way to optimize for you and so manual intervention will be
|
|
||||||
necessary.
|
|
||||||
|
|
||||||
This guide is intended to teach you some basics to help you
|
This guide is intended to teach you some basics to help you to [troubleshoot performance issues](docs/performance.html#profiling),
|
||||||
to troubleshoot performance issues, as well as discuss common sources of
|
as well as discuss [common sources of problems and their suggested solutions](docs/performance.html#common-sources-of-performance-problems).
|
||||||
problems and their suggested solutions.
|
|
||||||
|
|
||||||
### What you need to know about frames
|
## What you need to know about frames
|
||||||
|
|
||||||
Your grandparents' generation called movies ["moving
|
Your grandparents' generation called movies ["moving pictures"](https://www.youtube.com/watch?v=F1i40rnpOsA) for a reason:
|
||||||
pictures"](https://www.youtube.com/watch?v=F1i40rnpOsA) for a reason:
|
realistic motion in video is an illusion created by quickly changing static images at a consistent speed. We refer to each of these images as frames.
|
||||||
realistic motion in video is an illusion created by quickly changing
|
The number of frames that is displayed each second has a direct impact on how smooth and ultimately life-like a video (or user interface) seems to be.
|
||||||
static images at a consistent speed. We refer to each of these images as
|
iOS devices display 60 frames per second, which gives you and the UI system about 16.67ms to do all of the work needed to generate the static image (frame) that the user will see on the screen for that interval.
|
||||||
frames. The number of frames that is displayed each second has a direct
|
If you are unable to do the work necessary to generate that frame within the allotted 16.67ms, then you will "drop a frame" and the UI will appear unresponsive.
|
||||||
impact on how smooth and ultimately life-like a video (or user
|
|
||||||
interface) seems to be. iOS devices display 60 frames per second, which
|
|
||||||
gives you and the UI system about 16.67ms to do all of the work needed to
|
|
||||||
generate the static image (frame) that the user will see on the screen
|
|
||||||
for that interval. If you are unable to do the work necessary to
|
|
||||||
generate that frame within the allotted 16.67ms, then you will "drop a
|
|
||||||
frame" and the UI will appear unresponsive.
|
|
||||||
|
|
||||||
Now to confuse the matter a little bit, open up the developer menu in
|
Now to confuse the matter a little bit, open up the developer menu in your app and toggle `Show Perf Monitor`.
|
||||||
your app and toggle `Show Perf Monitor`. You will notice that there are
|
You will notice that there are two different frame rates.
|
||||||
two different frame rates.
|
|
||||||
|
|
||||||
#### JavaScript frame rate
|
![](img/PerfUtil.png)
|
||||||
|
|
||||||
For most React Native applications, your business logic will run on the
|
### JS frame rate (JavaScript thread)
|
||||||
JavaScript thread. This is where your React application lives, API calls
|
|
||||||
are made, touch events are processed, etc... Updates to native-backed
|
|
||||||
views are batched and sent over to the native side at the end of each iteration of the event loop, before the frame deadline (if
|
|
||||||
all goes well). If the JavaScript thread is unresponsive for a frame, it
|
|
||||||
will be considered a dropped frame. For example, if you were to call
|
|
||||||
`this.setState` on the root component of a complex application and it
|
|
||||||
resulted in re-rendering computationally expensive component subtrees,
|
|
||||||
it's conceivable that this might take 200ms and result in 12 frames
|
|
||||||
being dropped. Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it.
|
|
||||||
|
|
||||||
This often happens during Navigator transitions: when you push a new
|
For most React Native applications, your business logic will run on the JavaScript thread.
|
||||||
route, the JavaScript thread needs to render all of the components
|
This is where your React application lives, API calls are made, touch events are processed, etc...
|
||||||
necessary for the scene in order to send over the proper commands to the
|
Updates to native-backed views are batched and sent over to the native side at the end of each iteration of the event loop,
|
||||||
native side to create the backing views. It's common for the work being
|
before the frame deadline (if all goes well).
|
||||||
done here to take a few frames and cause jank because the transition is
|
If the JavaScript thread is unresponsive for a frame, it will be considered a dropped frame.
|
||||||
controlled by the JavaScript thread. Sometimes components will do
|
For example, if you were to call `this.setState` on the root component of a complex application and it resulted in re-rendering computationally expensive component subtrees,
|
||||||
additional work on `componentDidMount`, which might result in a second
|
it's conceivable that this might take 200ms and result in 12 frames being dropped.
|
||||||
stutter in the transition.
|
Any animations controlled by JavaScript would appear to freeze during that time.
|
||||||
|
If anything takes longer than 100ms, the user will feel it.
|
||||||
|
|
||||||
Another example is responding to touches: if you are doing work across
|
This often happens during `Navigator` transitions:
|
||||||
multiple frames on the JavaScript thread, you might notice a delay in
|
when you push a new route, the JavaScript thread needs to render all of the components necessary for the scene in order to send over the proper commands to the native side to create the backing views.
|
||||||
responding to TouchableOpacity, for example. This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread. As a result, TouchableOpacity cannot react to the touch events and command the native view to adjust its opacity.
|
It's common for the work being done here to take a few frames and cause [jank](http://jankfree.org/) because the transition is controlled by the JavaScript thread.
|
||||||
|
Sometimes components will do additional work on `componentDidMount`, which might result in a second stutter in the transition.
|
||||||
|
|
||||||
#### Main thread (aka UI thread) frame rate
|
Another example is responding to touches:
|
||||||
|
if you are doing work across multiple frames on the JavaScript thread, you might notice a delay in responding to `TouchableOpacity`, for example.
|
||||||
|
This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread.
|
||||||
|
As a result, `TouchableOpacity` cannot react to the touch events and command the native view to adjust its opacity.
|
||||||
|
|
||||||
Many people have noticed that performance of `NavigatorIOS` is better
|
### UI frame rate (main thread)
|
||||||
out of the box than `Navigator`. The reason for this is that the
|
|
||||||
animations for the transitions are done entirely on the main thread, and
|
|
||||||
so they are not interrupted by frame drops on the JavaScript thread.
|
|
||||||
([Read about why you should probably use Navigator
|
|
||||||
anyways.](docs/using-navigators.html)
|
|
||||||
|
|
||||||
Similarly, you can happily scroll up and down through a ScrollView when
|
Many people have noticed that performance of `NavigatorIOS` is better out of the box than `Navigator`.
|
||||||
the JavaScript thread is locked up because the ScrollView lives on the
|
The reason for this is that the animations for the transitions are done entirely on the main thread,
|
||||||
main thread (the scroll events are dispatched to the JS thread though,
|
and so they are not interrupted by frame drops on the JavaScript thread.
|
||||||
but their receipt is not necessary for the scroll to occur).
|
|
||||||
|
|
||||||
### Common sources of performance problems
|
Similarly, you can happily scroll up and down through a `ScrollView` when the JavaScript thread is locked up because the `ScrollView` lives on the main thread.
|
||||||
|
The scroll events are dispatched to the JS thread, but their receipt is not necessary for the scroll to occur.
|
||||||
|
|
||||||
#### Console.log statements
|
## Common sources of performance problems
|
||||||
|
|
||||||
When running a bundled app, these statements can cause a big bottleneck in the JavaScript thread. This includes calls from debugging libraries such as [redux-logger](https://github.com/evgenyrodionov/redux-logger), so make sure to remove them before bundling.
|
### Running in development mode (`dev=true`)
|
||||||
|
|
||||||
#### Development mode (dev=true)
|
|
||||||
|
|
||||||
JavaScript thread performance suffers greatly when running in dev mode.
|
JavaScript thread performance suffers greatly when running in dev mode.
|
||||||
This is unavoidable: a lot more work needs to be done at runtime to
|
This is unavoidable: a lot more work needs to be done at runtime to provide you with good warnings and error messages, such as validating propTypes and various other assertions.
|
||||||
provide you with good warnings and error messages, such as validating
|
|
||||||
propTypes and various other assertions.
|
|
||||||
|
|
||||||
#### Slow navigator transitions
|
### Using `console.log` statements
|
||||||
|
|
||||||
As mentioned above, `Navigator` animations are controlled by the
|
When running a bundled app, these statements can cause a big bottleneck in the JavaScript thread.
|
||||||
JavaScript thread. Imagine the "push from right" scene transition: each
|
This includes calls from debugging libraries such as [redux-logger](https://github.com/evgenyrodionov/redux-logger),
|
||||||
frame, the new scene is moved from the right to left, starting offscreen
|
so make sure to remove them before bundling.
|
||||||
(let's say at an x-offset of 320) and ultimately settling when the scene sits
|
|
||||||
at an x-offset of 0. Each frame during this transition, the
|
|
||||||
JavaScript thread needs to send a new x-offset to the main thread.
|
|
||||||
If the JavaScript thread is locked up, it cannot do this and so no
|
|
||||||
update occurs on that frame and the animation stutters.
|
|
||||||
|
|
||||||
Part of the long-term solution to this is to allow for JavaScript-based
|
### `ListView` initial rendering is too slow or scroll performance is bad for large lists
|
||||||
animations to be offloaded to the main thread. If we were to do the same
|
|
||||||
thing as in the above example with this approach, we might calculate a
|
|
||||||
list of all x-offsets for the new scene when we are starting the
|
|
||||||
transition and send them to the main thread to execute in an
|
|
||||||
optimized way. Now that the JavaScript thread is freed of this
|
|
||||||
responsibility, it's not a big deal if it drops a few frames while
|
|
||||||
rendering the scene -- you probably won't even notice because you will be
|
|
||||||
too distracted by the pretty transition.
|
|
||||||
|
|
||||||
Unfortunately this solution is not yet implemented, and so in the
|
Use the new [`FlatList`](docs/flatlist.html) or [`SectionList`](docs/sectionlist.html) component instead.
|
||||||
meantime we should use the InteractionManager to selectively render the
|
Besides simplifying the API, the new list components also have significant performance enhancements,
|
||||||
minimal amount of content necessary for the new scene as long as the
|
the main one being nearly constant memory usage for any number of rows.
|
||||||
animation is in progress. `InteractionManager.runAfterInteractions` takes
|
|
||||||
a callback as its only argument, and that callback is fired when the
|
|
||||||
navigator transition is complete (each animation from the `Animated` API
|
|
||||||
also notifies the InteractionManager, but that's beyond the scope of
|
|
||||||
this discussion).
|
|
||||||
|
|
||||||
Your scene component might look something like this:
|
TODO: Link to blog post
|
||||||
|
|
||||||
```js
|
### JS FPS plunges when re-rendering a view that hardly changes
|
||||||
class ExpensiveScene extends React.Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {renderPlaceholderOnly: true};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
If you are using a ListView, you must provide a `rowHasChanged` function that can reduce a lot of work by quickly determining whether or not a row needs to be re-rendered. If you are using immutable data structures, this would be as simple as a reference equality check.
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
this.setState({renderPlaceholderOnly: false});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
Similarly, you can implement `shouldComponentUpdate` and indicate the exact conditions under which you would like the component to re-render. If you write pure components (where the return value of the render function is entirely dependent on props and state), you can leverage PureRenderMixin to do this for you. Once again, immutable data structures are useful to keep this fast -- if you have to do a deep comparison of a large list of objects, it may be that re-rendering your entire component would be quicker, and it would certainly require less code.
|
||||||
if (this.state.renderPlaceholderOnly) {
|
|
||||||
return this._renderPlaceholderView();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
### Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time
|
||||||
<View>
|
|
||||||
<Text>Your full view goes here</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
"Slow Navigator transitions" is the most common manifestation of this, but there are other times this can happen. Using InteractionManager can be a good approach, but if the user experience cost is too high to delay work during an animation, then you might want to consider LayoutAnimation.
|
||||||
|
|
||||||
_renderPlaceholderView() {
|
The Animated api currently calculates each keyframe on-demand on the JavaScript thread, while LayoutAnimation leverages Core Animation and is unaffected by JS thread and main thread frame drops.
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text>Loading...</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
You don't need to be limited to rendering some loading indicator, you
|
One case where I have used this is for animating in a modal (sliding down from top and fading in a translucent overlay) while initializing and perhaps receiving responses for several network requests, rendering the contents of the modal, and updating the view where the modal was opened from. See the Animations guide for more information about how to use LayoutAnimation.
|
||||||
could alternatively render part of your content -- for example, when you
|
|
||||||
load the Facebook app you see a placeholder news feed item with grey
|
|
||||||
rectangles where text will be. If you are rendering a Map in your new
|
|
||||||
scene, you might want to display a grey placeholder view or a spinner
|
|
||||||
until the transition is complete as this can actually cause frames to be
|
|
||||||
dropped on the main thread.
|
|
||||||
|
|
||||||
#### ListView initial rendering is too slow or scroll performance is bad for large lists
|
|
||||||
|
|
||||||
This is an issue that comes up frequently because iOS ships with
|
|
||||||
UITableView which gives you very good performance by re-using underlying
|
|
||||||
UIViews. Work is in progress to do something similar with React Native,
|
|
||||||
but until then we have some tools at our disposal to help us tweak the
|
|
||||||
performance to suit our needs. It may not be possible to get all the way
|
|
||||||
there, but a little bit of creativity and experimentation with these
|
|
||||||
options can go a long way.
|
|
||||||
|
|
||||||
##### initialListSize
|
|
||||||
|
|
||||||
This prop specifies how many rows we want to render on our first render
|
|
||||||
pass. If we are concerned with getting *something* on screen as quickly
|
|
||||||
as possible, we could set the `initialListSize` to 1, and we'll quickly
|
|
||||||
see other rows fill in on subsequent frames. The number of rows per
|
|
||||||
frame is determined by the `pageSize`.
|
|
||||||
|
|
||||||
##### pageSize
|
|
||||||
|
|
||||||
After the initial render where `initialListSize` is used, ListView looks
|
|
||||||
at the `pageSize` to determine how many rows to render per frame. The
|
|
||||||
default here is 1 -- but if your views are very small and inexpensive to
|
|
||||||
render, you might want to bump this up. Tweak it and find what works for
|
|
||||||
your use case.
|
|
||||||
|
|
||||||
##### scrollRenderAheadDistance
|
|
||||||
|
|
||||||
"How early to start rendering rows before they come on screen, in pixels."
|
|
||||||
|
|
||||||
If we had a list with 2000 items and rendered them all immediately that
|
|
||||||
would be a poor use of both memory and computational resources. It would
|
|
||||||
also probably cause some pretty awful jank. So the scrollRenderAhead
|
|
||||||
distance allows us to specify how far beyond the current viewport we
|
|
||||||
should continue to render rows.
|
|
||||||
|
|
||||||
##### removeClippedSubviews
|
|
||||||
|
|
||||||
"When true, offscreen child views (whose `overflow` value is `hidden`)
|
|
||||||
are removed from their native backing superview when offscreen. This
|
|
||||||
can improve scrolling performance on long lists. The default value is
|
|
||||||
`true`."(The default value is `false` before version 0.14-rc).
|
|
||||||
|
|
||||||
This is an extremely important optimization to apply on large ListViews.
|
|
||||||
On Android the `overflow` value is always `hidden` so you don't need to
|
|
||||||
worry about setting it, but on iOS you need to be sure to set `overflow:
|
|
||||||
hidden` on row containers.
|
|
||||||
|
|
||||||
#### My component renders too slowly and I don't need it all immediately
|
|
||||||
|
|
||||||
It's common at first to overlook ListView, but using it properly is
|
|
||||||
often key to achieving solid performance. As discussed above, it
|
|
||||||
provides you with a set of tools that lets you split rendering of your
|
|
||||||
view across various frames and tweak that behavior to fit your specific
|
|
||||||
needs. Remember that ListView can be horizontal too.
|
|
||||||
|
|
||||||
#### JS FPS plunges when re-rendering a view that hardly changes
|
|
||||||
|
|
||||||
If you are using a ListView, you must provide a `rowHasChanged` function
|
|
||||||
that can reduce a lot of work by quickly determining whether or not a
|
|
||||||
row needs to be re-rendered. If you are using immutable data structures,
|
|
||||||
this would be as simple as a reference equality check.
|
|
||||||
|
|
||||||
Similarly, you can implement `shouldComponentUpdate` and indicate the
|
|
||||||
exact conditions under which you would like the component to re-render.
|
|
||||||
If you write pure components (where the return value of the render
|
|
||||||
function is entirely dependent on props and state), you can leverage
|
|
||||||
PureRenderMixin to do this for you. Once again, immutable data
|
|
||||||
structures are useful to keep this fast -- if you have to do a deep
|
|
||||||
comparison of a large list of objects, it may be that re-rendering your
|
|
||||||
entire component would be quicker, and it would certainly require less
|
|
||||||
code.
|
|
||||||
|
|
||||||
#### Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time
|
|
||||||
|
|
||||||
"Slow Navigator transitions" is the most common manifestation of this,
|
|
||||||
but there are other times this can happen. Using InteractionManager can
|
|
||||||
be a good approach, but if the user experience cost is too high to delay
|
|
||||||
work during an animation, then you might want to consider
|
|
||||||
LayoutAnimation.
|
|
||||||
|
|
||||||
The Animated api currently calculates each keyframe on-demand on the
|
|
||||||
JavaScript thread, while LayoutAnimation leverages Core Animation and is
|
|
||||||
unaffected by JS thread and main thread frame drops.
|
|
||||||
|
|
||||||
One case where I have used this is for animating in a modal (sliding
|
|
||||||
down from top and fading in a translucent overlay) while
|
|
||||||
initializing and perhaps receiving responses for several network
|
|
||||||
requests, rendering the contents of the modal, and updating the view
|
|
||||||
where the modal was opened from. See the Animations guide for more
|
|
||||||
information about how to use LayoutAnimation.
|
|
||||||
|
|
||||||
Caveats:
|
Caveats:
|
||||||
- LayoutAnimation only works for fire-and-forget animations ("static"
|
|
||||||
animations) -- if it must be be interruptible, you will need to use
|
|
||||||
Animated.
|
|
||||||
|
|
||||||
#### Moving a view on the screen (scrolling, translating, rotating) drops UI thread FPS
|
- LayoutAnimation only works for fire-and-forget animations ("static" animations) -- if it must be be interruptible, you will need to use `Animated`.
|
||||||
|
|
||||||
This is especially true when you have text with a transparent background
|
### Moving a view on the screen (scrolling, translating, rotating) drops UI thread FPS
|
||||||
positioned on top of an image, or any other situation where alpha
|
|
||||||
compositing would be required to re-draw the view on each frame. You
|
|
||||||
will find that enabling `shouldRasterizeIOS` or `renderToHardwareTextureAndroid`
|
|
||||||
can help with this significantly.
|
|
||||||
|
|
||||||
Be careful not to overuse this or your memory usage could go through the
|
This is especially true when you have text with a transparent background positioned on top of an image,
|
||||||
roof. Profile your performance and memory usage when using these props. If you don't plan to move a view anymore, turn this property off.
|
or any other situation where alpha compositing would be required to re-draw the view on each frame.
|
||||||
|
You will find that enabling `shouldRasterizeIOS` or `renderToHardwareTextureAndroid` can help with this significantly.
|
||||||
|
|
||||||
#### Animating the size of an image drops UI thread FPS
|
Be careful not to overuse this or your memory usage could go through the roof.
|
||||||
|
Profile your performance and memory usage when using these props.
|
||||||
|
If you don't plan to move a view anymore, turn this property off.
|
||||||
|
|
||||||
On iOS, each time you adjust the width or height of an Image component
|
### Animating the size of an image drops UI thread FPS
|
||||||
it is re-cropped and scaled from the original image. This can be very expensive,
|
|
||||||
especially for large images. Instead, use the `transform: [{scale}]`
|
|
||||||
style property to animate the size. An example of when you might do this is
|
|
||||||
when you tap an image and zoom it in to full screen.
|
|
||||||
|
|
||||||
#### My TouchableX view isn't very responsive
|
On iOS, each time you adjust the width or height of an Image component it is re-cropped and scaled from the original image.
|
||||||
|
This can be very expensive, especially for large images.
|
||||||
|
Instead, use the `transform: [{scale}]` style property to animate the size.
|
||||||
|
An example of when you might do this is when you tap an image and zoom it in to full screen.
|
||||||
|
|
||||||
Sometimes, if we do an action in the same frame that we are adjusting
|
### My TouchableX view isn't very responsive
|
||||||
the opacity or highlight of a component that is responding to a touch,
|
|
||||||
|
Sometimes, if we do an action in the same frame that we are adjusting the opacity or highlight of a component that is responding to a touch,
|
||||||
we won't see that effect until after the `onPress` function has returned.
|
we won't see that effect until after the `onPress` function has returned.
|
||||||
If `onPress` does a `setState` that results in a lot of work and a few
|
If `onPress` does a `setState` that results in a lot of work and a few frames dropped, this may occur.
|
||||||
frames dropped, this may occur. A solution to this is to wrap any action
|
A solution to this is to wrap any action inside of your `onPress` handler in `requestAnimationFrame`:
|
||||||
inside of your `onPress` handler in `requestAnimationFrame`:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
handleOnPress() {
|
handleOnPress() {
|
||||||
|
@ -304,14 +134,208 @@ handleOnPress() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Profiling
|
### Slow navigator transitions
|
||||||
|
|
||||||
Use the built-in Profiler to get detailed information about work done in
|
As mentioned above, `Navigator` animations are controlled by the JavaScript thread.
|
||||||
the JavaScript thread and main thread side-by-side.
|
Imagine the "push from right" scene transition:
|
||||||
|
each frame, the new scene is moved from the right to left,
|
||||||
|
starting offscreen (let's say at an x-offset of 320) and ultimately settling when the scene sits at an x-offset of 0.
|
||||||
|
Each frame during this transition, the JavaScript thread needs to send a new x-offset to the main thread.
|
||||||
|
If the JavaScript thread is locked up, it cannot do this and so no update occurs on that frame and the animation stutters.
|
||||||
|
|
||||||
For iOS, Instruments are an invaluable tool, and on Android you should
|
One solution to this is to allow for JavaScript-based animations to be offloaded to the main thread.
|
||||||
learn to use systrace.
|
If we were to do the same thing as in the above example with this approach,
|
||||||
|
we might calculate a list of all x-offsets for the new scene when we are starting the transition and send them to the main thread to execute in an optimized way.
|
||||||
|
Now that the JavaScript thread is freed of this responsibility,
|
||||||
|
it's not a big deal if it drops a few frames while rendering the scene -- you probably won't even notice because you will be too distracted by the pretty transition.
|
||||||
|
|
||||||
|
Solving this is one of the main goals behind the new [React Navigation](docs/navigation.html) library.
|
||||||
|
The views in React Navigation use native components and the [`Animated`](docs/animated.html) library to deliver 60 FPS animations that are run on the native thread.
|
||||||
|
|
||||||
|
## Profiling
|
||||||
|
|
||||||
|
Use the built-in profiler to get detailed information about work done in the JavaScript thread and main thread side-by-side.
|
||||||
|
Access it by selecting Perf Monitor from the Debug menu.
|
||||||
|
|
||||||
|
For iOS, Instruments is an invaluable tool, and on Android you should learn to use [`systrace`](docs/performance.html#profiling-android-ui-performance-with-systrace).
|
||||||
|
|
||||||
You can also use [`react-addons-perf`](https://facebook.github.io/react/docs/perf.html) to get insights into where React is spending time when rendering your components.
|
You can also use [`react-addons-perf`](https://facebook.github.io/react/docs/perf.html) to get insights into where React is spending time when rendering your components.
|
||||||
|
|
||||||
Another way to profile JavaScript is to use the Chrome profiler while debugging. This won't give you accurate results as the code is running in Chrome but will give you a general idea of where bottlenecks might be.
|
Another way to profile JavaScript is to use the Chrome profiler while debugging.
|
||||||
|
This won't give you accurate results as the code is running in Chrome but will give you a general idea of where bottlenecks might be.
|
||||||
|
|
||||||
|
But first, [**make sure that Development Mode is OFF!**](docs/performance.html#running-in-development-mode-dev-true) You should see `__DEV__ === false, development-level warning are OFF, performance optimizations are ON` in your application logs.
|
||||||
|
|
||||||
|
### Profiling Android UI Performance with `systrace`
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
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` 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 markers 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
|
||||||
|
|
||||||
|
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` is the length of time the trace will be collected in seconds
|
||||||
|
- `sched`, `gfx`, and `view` 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, and `view` gives you information about measure, layout, and draw passes
|
||||||
|
- `-a <your_package_name>` enables app-specific markers, specifically the ones built into the React Native framework. `your_package_name` can be found in the `AndroidManifest.xml` of your app and looks like `com.example.app`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
After opening the trace in your browser (preferably Chrome), you should see something like this:
|
||||||
|
|
||||||
|
![Example](img/SystraceExample.png)
|
||||||
|
|
||||||
|
> **HINT**:
|
||||||
|
> Use the WASD keys to strafe and zoom
|
||||||
|
|
||||||
|
If your trace .html file isn't opening correctly, check your browser console for the following:
|
||||||
|
|
||||||
|
![ObjectObserveError](img/ObjectObserveError.png)
|
||||||
|
|
||||||
|
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
|
||||||
|
- Selecting the html file generated from the previous command.
|
||||||
|
|
||||||
|
> **Enable VSync highlighting**
|
||||||
|
>
|
||||||
|
> Check this checkbox at the top right of the screen to highlight the 16ms frame boundaries:
|
||||||
|
>
|
||||||
|
> ![Enable VSync Highlighting](img/SystraceHighlightVSync.png)
|
||||||
|
>
|
||||||
|
> 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
- **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`, and `DispatchUI`:
|
||||||
|
|
||||||
|
![UI Thread Example](img/SystraceUIThreadExample.png)
|
||||||
|
|
||||||
|
- **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 like `JSCall`, `Bridge.executeJSCall`, etc:
|
||||||
|
|
||||||
|
![JS Thread Example](img/SystraceJSThreadExample.png)
|
||||||
|
|
||||||
|
- **Native Modules Thread.**
|
||||||
|
This is where native module calls (e.g. the `UIManager`) are executed.
|
||||||
|
The thread name will be either `mqt_native_modules` or `<...>`.
|
||||||
|
To identify it in the latter case, look for things like `NativeCall`, `callJavaModuleMethod`, and `onBatchComplete`:
|
||||||
|
|
||||||
|
![Native Modules Thread Example](img/SystraceNativeModulesThreadExample.png)
|
||||||
|
|
||||||
|
- **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 like `DrawFrame` and `queueBuffer`:
|
||||||
|
|
||||||
|
![Render Thread Example](img/SystraceRenderThreadExample.png)
|
||||||
|
|
||||||
|
#### Identifying a culprit
|
||||||
|
|
||||||
|
A smooth animation should look something like the following:
|
||||||
|
|
||||||
|
![Smooth Animation](img/SystraceWellBehaved.png)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you noticed chop, however, you might see something like this:
|
||||||
|
|
||||||
|
![Choppy Animation from JS](img/SystraceBadJS.png)
|
||||||
|
|
||||||
|
Notice that the JS thread is executing basically 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:
|
||||||
|
|
||||||
|
![Choppy Animation from UI](img/SystraceBadUI.png)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Resolving JavaScript issues
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
![Too much JS](img/SystraceBadJS2.png)
|
||||||
|
|
||||||
|
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](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate).
|
||||||
|
|
||||||
|
#### Resolving native UI Issues
|
||||||
|
|
||||||
|
If you identified a native UI problem, there are usually two scenarios:
|
||||||
|
|
||||||
|
1. the UI you're trying to draw each frame involves to much work on the GPU, or
|
||||||
|
2. You're constructing new UI during the animation/interaction (e.g. loading in new content during a scroll).
|
||||||
|
|
||||||
|
##### Too much GPU work
|
||||||
|
|
||||||
|
In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this:
|
||||||
|
|
||||||
|
![Overloaded GPU](img/SystraceBadUI.png)
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- investigate using `renderToHardwareTextureAndroid` for complex, static content that is being animated/transformed (e.g. the `Navigator` slide/alpha animations)
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
If these don't help and you want to dig deeper into what the GPU is actually doing, you can check out [Tracer for OpenGL ES](http://developer.android.com/tools/help/gltracer.html).
|
||||||
|
|
||||||
|
##### Creating new views on the UI thread
|
||||||
|
|
||||||
|
In the second scenario, you'll see something more like this:
|
||||||
|
|
||||||
|
![Creating Views](img/SystraceBadCreateUI.png)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
There isn't an easy 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 a 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.
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Platform Specific Code
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/platform-specific-code.html
|
permalink: docs/platform-specific-code.html
|
||||||
next: gesture-responder-system
|
next: debugging
|
||||||
previous: upgrading
|
previous: colors
|
||||||
---
|
---
|
||||||
|
|
||||||
When building a cross-platform app, you'll want to re-use as much code as possible. Scenarios may arise where it makes sense for the code to be different, for example you may want to implement separate visual components for iOS and Android.
|
When building a cross-platform app, you'll want to re-use as much code as possible. Scenarios may arise where it makes sense for the code to be different, for example you may want to implement separate visual components for iOS and Android.
|
||||||
|
|
|
@ -4,8 +4,9 @@ title: Running On Device
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/running-on-device.html
|
permalink: docs/running-on-device.html
|
||||||
next: javascript-environment
|
banner: ejected
|
||||||
previous: testing
|
next: upgrading
|
||||||
|
previous: integration-with-existing-apps
|
||||||
---
|
---
|
||||||
|
|
||||||
It's always a good idea to test your app on an actual device before releasing it to your users. This document will guide you through the necessary steps to run your React Native app on a device.
|
It's always a good idea to test your app on an actual device before releasing it to your users. This document will guide you through the necessary steps to run your React Native app on a device.
|
||||||
|
|
|
@ -4,6 +4,7 @@ title: Running On Simulator
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (iOS)
|
category: Guides (iOS)
|
||||||
permalink: docs/running-on-simulator-ios.html
|
permalink: docs/running-on-simulator-ios.html
|
||||||
|
banner: ejected
|
||||||
next: communication-ios
|
next: communication-ios
|
||||||
previous: linking-libraries-ios
|
previous: linking-libraries-ios
|
||||||
---
|
---
|
||||||
|
|
|
@ -4,7 +4,8 @@ title: Generating Signed APK
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides (Android)
|
category: Guides (Android)
|
||||||
permalink: docs/signed-apk-android.html
|
permalink: docs/signed-apk-android.html
|
||||||
next: android-ui-performance
|
banner: ejected
|
||||||
|
next: android-building-from-source
|
||||||
previous: headless-js-android
|
previous: headless-js-android
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ title: Testing
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/testing.html
|
permalink: docs/testing.html
|
||||||
next: running-on-device
|
next: understanding-cli
|
||||||
previous: debugging
|
previous: gesture-responder-system
|
||||||
---
|
---
|
||||||
|
|
||||||
This document is about running tests on React Native itself. If you're interested in testing a React Native app, check out the [React Native Tutorial](http://facebook.github.io/jest/docs/tutorial-react-native.html) on the Jest website.
|
This document is about running tests on React Native itself. If you're interested in testing a React Native app, check out the [React Native Tutorial](http://facebook.github.io/jest/docs/tutorial-react-native.html) on the Jest website.
|
||||||
|
|
|
@ -4,7 +4,7 @@ title: Timers
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/timers.html
|
permalink: docs/timers.html
|
||||||
next: direct-manipulation
|
next: javascript-environment
|
||||||
previous: accessibility
|
previous: accessibility
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ title: Understanding the CLI
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/understanding-cli.html
|
permalink: docs/understanding-cli.html
|
||||||
next: upgrading
|
banner: ejected
|
||||||
previous: performance
|
next: integration-with-existing-apps
|
||||||
|
previous: running-on-device
|
||||||
---
|
---
|
||||||
|
|
||||||
Though you may have installed the `react-native-cli` via npm as a separate module, it is a shell for accessing the CLI embedded
|
Though you may have installed the `react-native-cli` via npm as a separate module, it is a shell for accessing the CLI embedded
|
||||||
|
@ -35,4 +36,3 @@ module.exports = {
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
The command name identifies the parameters that a command would expect. When the command parameter is surrounded by greater-than, less-than symbols `< >`, this indicates that the parameter is expected. When a parameter is surrounded by brackets `[ ]`, this indicates that the parameter is optional.
|
The command name identifies the parameters that a command would expect. When the command parameter is surrounded by greater-than, less-than symbols `< >`, this indicates that the parameter is expected. When a parameter is surrounded by brackets `[ ]`, this indicates that the parameter is optional.
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ title: Upgrading
|
||||||
layout: docs
|
layout: docs
|
||||||
category: Guides
|
category: Guides
|
||||||
permalink: docs/upgrading.html
|
permalink: docs/upgrading.html
|
||||||
next: platform-specific-code
|
banner: ejected
|
||||||
|
next: native-modules-ios
|
||||||
previous: understanding-cli
|
previous: understanding-cli
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -698,7 +698,7 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
float: left;
|
float: left;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
margin: 5px 48px 0 0; }
|
margin: 0 48px 0 0; }
|
||||||
.nav-docs ul {
|
.nav-docs ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -722,7 +722,7 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
margin-top: 12px;
|
margin-top: 0;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
|
@ -1539,6 +1539,22 @@ table.versions {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: rgba(5, 165, 209, 0.05); }
|
background-color: rgba(5, 165, 209, 0.05); }
|
||||||
|
|
||||||
|
.banner-crna-ejected {
|
||||||
|
border: 1px solid #05A5D1;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 40px; }
|
||||||
|
.banner-crna-ejected h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: #05A5D1;
|
||||||
|
color: white; }
|
||||||
|
.banner-crna-ejected p {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 2px;
|
||||||
|
text-decoration: none !important;
|
||||||
|
background-color: white; }
|
||||||
|
|
||||||
.prism {
|
.prism {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace;
|
font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
$color-react-native-blue: #05A5D1 !default;
|
$color-react-native-blue: #05A5D1 !default;
|
||||||
|
$color-react-native-blue-light: #F5FCFF !default;
|
||||||
$color-react-native-blue-dark: #0484A7 !default;
|
$color-react-native-blue-dark: #0484A7 !default;
|
||||||
$color-react-native-blue-darker: #025268 !default;
|
$color-react-native-blue-darker: #025268 !default;
|
||||||
$color-react-native-gray: #3B3738 !default;
|
$color-react-native-gray: #3B3738 !default;
|
||||||
$color-react-native-gray-dark: #2D2D2D !default;
|
$color-react-native-gray-dark: #2D2D2D !default;
|
||||||
$color-react-native-gray-darkest: #222 !default;
|
$color-react-native-gray-darkest: #222 !default;
|
||||||
$color-react-native-blue-light: #F5FCFF !default;
|
|
||||||
|
|
||||||
$color-nav-bg: $color-react-native-gray-darkest;
|
$color-nav-bg: $color-react-native-gray-darkest;
|
||||||
$color-hero-bg: $color-react-native-gray-dark;
|
$color-hero-bg: $color-react-native-gray-dark;
|
||||||
|
|
|
@ -377,7 +377,7 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
float: left;
|
float: left;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
margin: 5px 48px 0 0;
|
margin: 0 48px 0 0;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -413,7 +413,7 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
margin-top: 12px;
|
margin-top: 0;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: $color-sidenav-header-bg;
|
background-color: $color-sidenav-header-bg;
|
||||||
|
@ -1414,6 +1414,27 @@ table.versions {
|
||||||
background-color: rgba(5, 165, 209, 0.05);
|
background-color: rgba(5, 165, 209, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.banner-crna-ejected {
|
||||||
|
border: 1px solid $color-react-native-blue;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: $color-react-native-blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 2px;
|
||||||
|
text-decoration: none !important;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@import "lib/vendor/prism";
|
@import "lib/vendor/prism";
|
||||||
@import "lib/vendor/algolia";
|
@import "lib/vendor/algolia";
|
||||||
@import "hero";
|
@import "hero";
|
||||||
|
|
Loading…
Reference in New Issue