React Native app Performance Optimization from code level
React Native four Threads
There are three primary threads that developers need to be aware of.
In addition, a Render Thread is available for Android 5.0 and above. Each thread plays a distinct role in how your React Native application functions.
Main (UI) Thread
This is the primary thread where all native UI components are created and manipulated. It handles user interactions, renders UI components, and manages device screen updates.
Every React Native UI update happens on this thread. Therefore, if you’re manipulating your state frequently, this thread can become busy and cause performance issues.
This thread’s primary role is to keep the interface smooth and responsive. An example is animating a member using the Animated API.
Animated.timing(this.state.fadeAnim, {
// this executes on the UI thread
toValue: 1,
duration: 2000,
}).start();
In the above code snippet, Animated.timing updates the component’s opacity over two seconds. This animation occurs on the Main Thread to ensure smooth UI updates.
JavaScript Thread
React Native applications execute JavaScript code in a separate JavaScript engine, which happens on the JavaScript thread. This includes API calls, handling touch events, and executing JavaScript code.
This is the thread where your actual React and JavaScript code gets executed. An example would be setting the state after fetching data from an API.
fetchData = async () => {
const response = await fetch("https://api.example.com/data");
const json = await response.json();
this.setState({ data: json }); // this line is executed in the JS thread
};
This entire operation runs on the JavaScript Thread.
Native Modules Thread
React Native allows you to write code in native languages (like Java for Android and Objective-C or Swift for iOS) when performing tasks without JavaScript. That is known as a native module, and the execution of this native code happens in the Native Modules thread.
If you’re using native code in your React Native app, it gets executed here. The native modules thread can also offload heavy computations from the JavaScript thread to keep your application responsive.
A simple example would be creating a Toast module in Android.
// This is Java code that will run on the Native Modules Thread
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
In this example, the show method will be invoked from JavaScript but run on the Native Modules thread.
Render Thread (Android 5.0+)
This thread was introduced in Android 5.0 (Lollipop). It takes rendering off the Main Thread for apps built to take advantage of it. It is especially beneficial for complex animations that need a high frame rate.
Profiling Introduction
Profiling is essential to understanding the runtime performance of the app through analysis that measures the memory or time complexity, frequency, and duration of function calls, etc. Getting all this information helps you to track down and provide proper solutions to keep your app healthy and your users engaged.
70% of the users will leave your app if the response to a given action takes too long. And once they do, they aren’t likely to return ever again, which can seriously harm your business. For that reason alone, profiling your React Native application with native tools for iOS and Android may turn out a game changer — for both your organization and your user base.
Profiling iOS
Xcode provides some basic tools to do the first report. You can monitor the CPU, Memory, and Network.
After running your app from Xcode, you will see the performance monitor from Xcode “Debug Navigator” button at top of the the left side bar like below.
Let’s analysis & know what each monitor means from this “Performance Monitor”
Let’s click on CPU Monitor. It will show you something like below.
Let’s click on Memory Monitor now & It will show you something like below.
Let’s click on Disk Monitor now & It will show you something like below
Let’s click on Network Monitor now & It will show you something like below.
Xcode provides an extra monitor that isn’t shown by default but can help you inspect your UI — it’s the View Hierarchy. When the app is running, and you are on the screen you want to inspect, click on Debug View UI Hierarchy button like below. This will show your current UI in a 2D/3D model and the view tree.
This will help you to detect overlappings (you can’t see a component) or if you want to flatten your component tree. Even though RN does a view flattening, it sometimes can’t do it with all of them, so here we can do some optimization focusing on specific items.
Profiling iOS by Xcode Instruments
From Xcode “Open Developer Tool” open “Instruments” like below.
After clicking on “Instruments” you will see a panel like below.
We are going to use Time Profiler from here. Let’s dive into it. Click & open Time Profiler. After opening Time Profiler you will see something like below.
From the top left corner of Time Profiler, you will see something like below (Look at Red rectangle ?? and Green rectangle ??).
If you click on these Red rectangle ?? and Green rectangle ?? then you will see dropdown & from dropdown select your device & select your app accordingly like below.
You already saw a START (??) button at the left side of your selected Device. Just click on it & use your app as usual. For my case I loaded a list of items in a FlatList in my simulator.
I used my app for 40 seconds to load the List & scrawling it ups & down constantly to get any time killer issue. After stopping the “Profiler Recording” I found a big blue rectangle (see below image), which means there is something that is taking a lot of time (for my case blue rectangle took around 3 seconds) to finish.
Let’s examine the threads. The threads section shows all the activity done for the entire profiling duration (for my case, it was 40 seconds). You can select any specific part of the chart to check the threads and their activity. Use your mouse left-click button on the graph to choose the range of the graph you want to inspect. I selected only the red coloured part as shown in the image below.
As shown in the image above, only a very small amount of activity list (green coloured box ??) is displayed for the selected part (red coloured box ??) of the graph.
You can expand by pressing option + mouse click over the chevron (??), which will expand to display useful information. At least for now, it is showing the memory address, but we will need to find another way to find where the problem is.
Flipper for JS Context tracking
Let’s use Flipper and pair it with a monitor called Hermes Debugger (RN). With the app open and running, we go to Flipper, select the running app if not selected already, and go to Hermes Debugger (RN) -> Profiler.
Let’s first install flipper in MacOS. After successfully installing, we will see how we can pair with Hermes Debugger (RN).
Install Flipper
To install Flipper use either Homebrew command or download .dmg file of Flipper from official flipper download link.
Troubleshoot Flipper Issue
After installing flipper, if you find this below error while opening Flipper app in your MacOS, then there is a simple solution.
Solution: Just run this command xattr -d com.apple.quarantine /Applications/Flipper.app in the root directory of your MacBook terminal (For my case it was iTerm). Now again open the Flipper app.
领英推荐
After opening Flipper now you may see something like below. An another error ????♂?.
But, what I did is just completely closed my Flipper App by Cmd + Q. Then again I opened Flipper from the launchpad. It worked for me now ??. This is what Flipper gave me at it’s first view.
Ohh yea, you see already the tab “Hermes Debugger (RN)” under the section React Native in Flipper Home page. Pretty Cool ??.
Let’s back on where we last stopped & started installing Flipper. Yea that was “Pair Flipper with Hermes Debugger (RN) -> Profiler”.
?? Note: Always remember that, run your app (iOS/ Android) in your device/ simulator before opening Flipper app.
Profiling iOS by Flipper with “Hermes Debugger (RN)”
When i clicked on “Hermes Debugger (RN)” button it shows me a text like below.
As my React native app was in v0.71 so by default Hermes was true but still Flipper says that it didn’t find any Hermes App. So, I just reloaded my app in my simulator. Now it connected successfully & when i again click on “Hermes Debugger (RN)” button then it shows something like below.
If you you didn’t enable Hermes in your React native app or you want to check “Is Hermes already enabled in your app ?” then see my short Article on it. Article link: Hermes Details ??
Now, we click start, so the profiler begins. We do the same flow and actions in app as before when profiling with Time Profiler. When we stop, we will see all the data collected.
By default, the data will be sorted from bottom to top with the heavy tasks at the top. In my case, I see that a function called checkType() takes the maximum time, which is only 122 milliseconds. So I understand that the code that I wrote in my React Native app is very efficient for the FlatList that I profiled.
I also learned from the previous “Time Profiler” from Xcode Instruments that the big blue time-consuming action was an API call that fetched the list data. So I need to optimize my backend code instead of my React Native code. After a good optimization in my backend, I don’t see that big blue in the graph anymore. Below is my final output from Xcode “Time Profiler” Instruments where i don’t see the big blue time consuming actions anymore ??.
Profiling Android
In the event of any performance issues, we mostly use React Profiler to troubleshoot and resolve our problems. Since most of the performance problems originate from the JS realm, we don’t usually need to do anything beyond that. But sometimes, we’ll encounter a bug or performance issue that comes directly from the Android runtime. In such a case, we’ll need a fine profiling tool to help us gather the following metrics from the device:
Based on that data, we can check whether our app consumes more energy than usual or, in some cases, uses more CPU power than it should. It is useful especially to check the executed code on lower-end (LE) Android devices. Some algorithms can run faster on some devices, and the end user will not spot any glitches, but we have to remember some customers can use LE devices, and the algorithm or function can be too heavy for their phones. High-end devices will handle it because their hardware is powerful.
Android Profiler in Android Studio
Android Studio is the IDE developed by JetBrains. It is officially supported by Google and the official IDE, which can be used to develop any Android app. It is very powerful and contains lots of functionalities in one place. One of those tools is Android Profiler, which, as the name suggests, comes in handy if you’re in need of React Native profiling on Android.
If you have not installed Android Studio yet, you can install it using this link.
After installing Android Studio, just open your React native app android folder by Android Studio. Now give Android Studio some time to complete all of it’s dependency of Gradle for your react Native app. it will take some time (may be even more than 10 minutes).
After successfully installing all dependency open the Profiler like below, choose View > Tool Windows > Profiler from the Android Studio top menu bar.
Or you can click on Profile button in the toolbar.
Before you start profiling the app, please remember these
Steps to make “JS DEV MODE” off ??
And, here is the picture of METRO last line.
Start Profiling
Now, go to the Profiler tab (2 way shown before how to open profiler or from at the bottom of Android Studio) and add a new profiler session like below. In Profiler Session you will find your device/ simulator running.
Wait for the session to attach to your app and start performing actions that could cause some performance issues, like swiping, scrolling, navigating, etc. Once you’re done, you should see some metrics like below.
Each greenfield React Native app has only one Android Activity. If your app has more than one, it’s most likely a brownfield one. Read more about the brownfield approach here. In the above example, we don’t see anything interesting. Everything works finewithout any glitches. Let’s check each metric:
Use Android Profiler in action
In the previous example, we could see some relations between each metric.
To see a more detailed view, we have to double-click on the tab. Now we can see more details. When the user started to do some touch action (swiping in the above example), we could see more CPU work. Each app will have its own signature of CPU spikes and lows. It’s important to build an intuition about it, by interacting with it and pairing certain activities, like touch events, with the increased usage. In other words, some spikes are expected, because the work needs to be done. The problem starts when CPU usage is very high for extended periods or in unexpected places.
Let’s imagine you would like to pick the best list or scroll view component for your React Native app, which has the best performance on a lower-end device. You noticed the current solutions could be revamped or improved, and you started working on this. In your experiment, you would like to check how your solution works for LE devices using the above-described solution. When you double-clicked on CPU, you could spot the below data.
So, what i found here is RenderThread taking time. If you’re using Android L (5.0) and up, you will have a RenderThread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either RenderThreador <...>.
OpenGL is an open source cross-platform API for rendering 2D and 3D graphics. It is used by React Native to create native modules that can access the GPU and perform complex computations. React Native uses OpenGL to enable features such as animations, transitions, and 3D effects in mobile applications.
Let’s see what about our mqt_js thread from the profiling chart. See below.
If you see somehow mqt_js thread shows toom much green field, then mqt_js thread used almost all the time and does some heavy computation because your computations are done on the JS side. You can start thinking about how to improve it. There are multiple options to check.
You can try out all of those solutions and compare the effect between each other. The profiler will provide you with a metric, and based on that, you can decide which approach fits best to your particular problem.
System Tracing with Android Studio CPU Profiler
Using the Android Studio CPU Profiler, we can also make a system tracing. We can check when the appropriate function has been called. We can triage all threads and see which function is the costliest, which affects the UX. To enable system tracing, click on the CPU section (while profiling session record is running) and select System Trace Recording. Then click on “Record”.
After some interaction in your app & stop the record, you should be able to see all the threads with details
You can also save your data by clicking the Save Button.
You can now use the saved data in a different tool, e.g., Perfetto.
Flipper performance plugin for Android
In iOS Profiling part of above we saw how to install Flipper & use it in debugging. In Flipper you can use this as a Flipper Plugin Flashlight. But it will be better if you use Flashlight from your CLI. Follow below steps to install Flashlight & start it into web.
Replacing Flipper in 0.73+ ?
Don’t worry for upcoming versions of React Native. you can still use Flipper with 0.73+; you will just have to install it yourself because it won’t come preinstalled.
? Software Engineer | Backend | Devops | Database Optimization | AWS Cloud | Azure Cloud | Java | Let's connect?
8 个月Great breakdown. Thanks for sharing this valuable guide??