A brief analysis of React Native startReactApplication method

A brief analysis of React Native startReactApplication method

In this article, we sorted out the startup process of RN . Since the final startReactApplication is relatively complex and involves the process of finally executing the front-end js , we extracted it separately and analyzed it in an independent article.

First, let's look at where startReactApplication is called:

mReactRootView.startReactApplication(
    getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);

You can see that startReactApplication is called on rootView , and the input parameters are instanceManager、appKey、mLaunchOptions .

Follow startReactApplication to find out its call chain:

mReactInstanceManager.createReactContextInBackground() -> recreateReactContextInBackgroundInner() -> recreateReactContextInBackgroundFromBundleLoader() -> recreateReactContextInBackground() -> runCreateReactContextOnNewThread()

recreateReactContextInBackground is a method in ReactInstanceManager that does two things:

1. Create a ReactContextInitParams instance initParams , as shown below. Its input parameter jsExecutorFactory is passed in when creating ReactInstanceManager .

final ReactContextInitParams initParams =
    new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);

2. Call runCreateReactContextOnNewThread

runCreateReactContextOnNewThread is a method in ReactInstanceManager , which mainly does two things:

  1. Create a new thread and create ReactContext context through createReactContext in the new thread;
  2. The context environment is set up through setupReactContext , and finally AppRegistry.js is called to start the App.

createReactContext

First look at where it is called:

final ReactApplicationContext reactApplicationContext =
    createReactContext(
        initParams.getJsExecutorFactory().create(),
        initParams.getJsBundleLoader());

Its two input parameters are JavaScriptExecutor instance created by JsExecutorFactory and JsBundleLoader instance.

JavaScriptExecutor

The first input parameter startReactApplication is getReactNativeHost().getReactInstanceManager() to get ReactInstanceManager instance. There is only one ReactInstanceManager instance in the RN application, which was created earlier when MainActivity was created.

Looking back at the React Native startup process, the following method is actually called during the creation process:

ReactInstanceManager reactInstanceManager = builder.build()

builder is ReactInstanceManagerBuilder . We come to the build method of this class and find that it finally executes return new ReactInstanceManager(...) . The fourth parameter in the construction parameter is: getDefaultJSExecutorFactory . We come to its definition:

 private JavaScriptExecutorFactory getDefaultJSExecutorFactory(
      String appName, String deviceName, Context applicationContext) {
    try {
      // If JSC is included, use it as normal
      initializeSoLoaderIfNecessary(applicationContext);
      SoLoader.loadLibrary("jscexecutor");
      return new JSCExecutorFactory(appName, deviceName);
    } catch (UnsatisfiedLinkError jscE) { /* ... */ }
}

That is to say, when we create ReactInstanceManagerBuilder , we create JSCExecutorFactory and then call its create method to create JSCExecutor . JSCExecutorFactory implements the JavaScriptExecutorFactory interface. Its create method is as follows, returning JSCExecutor instance:

 @Override
  public JavaScriptExecutor create() throws Exception {
    WritableNativeMap jscConfig = new WritableNativeMap();
    jscConfig.putString("OwnerIdentity", "ReactNative");
    jscConfig.putString("AppIdentity", mAppName);
    jscConfig.putString("DeviceIdentity", mDeviceName);
    return new JSCExecutor(jscConfig);
  }

Looking down at the definition of JSCExecutor , it inherits from JavaScriptExecutor class:

@DoNotStrip
/* package */ class JSCExecutor extends JavaScriptExecutor {
  static {
    SoLoader.loadLibrary("jscexecutor");
  }
  /* package */ JSCExecutor(ReadableNativeMap jscConfig) {
    super(initHybrid(jscConfig));
  }
  @Override
  public String getName() {
    return "JSCExecutor";
  }
  private static native HybridData initHybrid(ReadableNativeMap jscConfig);
}

So it is clear that the first parameter of createReactContext is a JSCExecutor instance, which is a C++ module loaded by SoLoader .

JsBundleLoader

Similarly, in return new ReactInstanceManager(...) , the fifth parameter in its construction parameters is: JSBundleLoader.createAssetLoader(mApplication, mJSBundleAssetUrl, false)

Coming to its definition, I found that it returns a JSBundleLoader instance and overrides its loadScript method.

public static JSBundleLoader createAssetLoader(
    final Context context, final String assetUrl, final boolean loadSynchronously) {
  return new JSBundleLoader() {
    @Override
    public String loadScript(JSBundleLoaderDelegate delegate) {
      delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
      return assetUrl;
    }
  };
}

After creating the JSCExecutor instance and JSBundleLoader instance, we officially enter the createReactContext method.

createReactContext

private ReactApplicationContext createReactContext(
  final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);

  CatalystInstanceImpl.Builder catalystInstanceBuilder = /* ... */

  try {
    catalystInstance = catalystInstanceBuilder.build();
  } finally { /* ... */ }

  reactContext.initializeWithInstance(catalystInstance);

  TurboModuleManager turboModuleManager =
    new TurboModuleManager( /* ... */ )

  catalystInstance.setTurboModuleManager(turboModuleManager);

  if (mJSIModulePackage != null) {
    catalystInstance.addJSIModules( /* ... */ );
  }

  catalystInstance.runJSBundle();
  return reactContext;

In it, reactContext is first created, and catalystInstance is created through catalystInstanceBuilder . Then, reactContext and catalystInstance are associated through the initializeWithInstance method, and a series of work is performed to initialize catalystInstance . Finally, enter the method catalystInstance.runJSBundle() .

initializeWithInstance

By calling getUIQueueThread , getNativeModulesQueueThread , and getJSQueueThread , three thread queues are created, namely UI thread, NativeModules thread, and JS thread.

runJSBundle

public void runJSBundle() {
  mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
  synchronized (mJSCallsPendingInitLock) {
    mAcceptCalls = true;
    for (PendingJSCall function : mJSCallsPendingInit) {
      function.call(this);
    }
    mJSCallsPendingInit.clear();
    mJSBundleHasLoaded = true;
  }
  Systrace.registerListener(mTraceListener);
}

Execute its loadScript method through mJSBundleLoader returned previously:

public String loadScript(JSBundleLoaderDelegate delegate) {
  delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
  return assetUrl;
}

loadScriptFromAssets method is in CatalystInstanceImpl :

public void loadScriptFromAssets(
    AssetManager assetManager, String assetURL, boolean loadSynchronously) {
  mSourceURL = assetURL;
  jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously);
}

assetURL here is passed in when createAssetLoader creates mJSBundleLoader , and its assignment time is in the reactInstanceManagerBuilder instance, by the createReactInstanceManager method of reactNativeHost instance. If the developer has customized assetURL by overriding the getJSBundleFile method in MainApplication.java , that URL will be used. Otherwise, the system default will be used, such as file://sdcard/myapp_cache/index.android.bundle .

The jniLoadScriptFromAssets method is defined on the C++ side and is used to read js files. Why can C++ methods be called directly from Java code? This is a question that will be explained later when analyzing the communication between Java and C++ and between Java and JS.

reactContext is created through createReactContext , the catalystInstance instance is created, and the two are associated, and then the js file is read through catalystInstance . Next, we will enter setupReactContext stage.

setupReactContext

private void setupReactContext(final ReactApplicationContext reactContext) {
    synchronized (mAttachedReactRoots) {
      catalystInstance.initialize();
      for (ReactRoot reactRoot : mAttachedReactRoots) {
        if (reactRoot.getState().compareAndSet(ReactRoot.STATE_STOPPED, ReactRoot.STATE_STARTED)) {
          attachRootViewToInstance(reactRoot);
        }
      }
    }
    UiThreadUtil.runOnUiThread(
      public void run() {
        listener.onReactContextInitialized(reactContext);
      }
    )
    reactContext.runOnJSQueueThread(
      public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
      }
    )
    reactContext.runOnNativeModulesQueueThread(
      public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
      }
    )
}

Here’s what’s happening here:

  • catalystInstance.initialize(): Initialization of all native modules
  • attachRootViewToInstance(reactRoot): Draw all RootViews and add them to the corresponding instances and set the corresponding listening events
  • Create UI module, JS module and native module threads, and set the priority of the threads where the JS module and native module are located

Summary of this article

Starting from the source code of the createReactContext and setupReactContext methods, the execution process of the RN startReactApplication method is analyzed, including:

The main function of createReactContext is to create reactContext , create catalystInstance instance, associate the two, and then read the js file through catalystInstance .

The main function of setupReactContext is to initialize all native modules, draw all rootviews, create UI module, JS module and native module threads, and set priorities.

This is the end of this article about the React Native startReactApplication method. For more related React Native startReactApplication content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • React native ScrollView pull down refresh effect
  • React Native startup process detailed analysis
  • In-depth understanding of React Native custom routing management
  • How to use Lottie animation in React Native project

<<:  Analysis of Alibaba Cloud CentOS7 server nginx configuration and FAQs

>>:  Summary of basic knowledge points of MySql database

Recommend

Docker+nextcloud to build a personal cloud storage system

1. Docker installation and startup yum install ep...

WeChat applet learning wxs usage tutorial

What is wxs? wxs (WeiXin Script) is a scripting l...

Tutorial on installing Ubuntu 20.04 and NVIDIA drivers

Install Ubuntu 20.04 Install NVIDIA drivers Confi...

Regular expression usage in CSS selectors

Yes, CSS has regular expressions too (Amen) Two p...

How to install and configure the supervisor daemon under centos7

Newbie, record it yourself 1. Install supervisor....

How to ensure that every page of WeChat Mini Program is logged in

Table of contents status quo Solution Further sol...

How to use docker to deploy spring boot and connect to skywalking

Table of contents 1. Overview 1. Introduction to ...

Native JS to implement real-time clock

Share a real-time clock effect implemented with n...

Detailed explanation of Object.create instance usage in js

1. Create a new object using the Object.create() ...

Introduction to Spark and comparison with Hadoop

Table of contents 1. Spark vs. Hadoop 1.1 Disadva...

Implementation of fuzzy query like%% in MySQL

1, %: represents any 0 or more characters. It can...

Discussion on the numerical limit of the ol element in the html document

Generally speaking, it is unlikely that you will ...

Detailed explanation of how to use zabbix to monitor oracle database

1. Overview Zabbix is ​​a very powerful and most ...