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

W3C Tutorial (12): W3C Soap Activity

Web Services are concerned with application-to-ap...

v-for directive in vue completes list rendering

Table of contents 1. List traversal 2. The role o...

Ubuntu installation Matlab2020b detailed tutorial and resources

Table of contents 1. Resource files 2. Installati...

MySQL advanced learning index advantages and disadvantages and rules of use

1. Advantages and Disadvantages of Indexes Advant...

Steps to deploy Docker project in IDEA

Now most projects have begun to be deployed on Do...

How much data can be stored in a MySQL table?

Programmers must deal with MySQL a lot, and it ca...

CSS hacks \9 and \0 may not work for hacking IE11\IE9\IE8

Every time I design a web page or a form, I am tr...

7 ways to vertically center elements with CSS

【1】Know the width and height of the centered elem...

Markup Language - Image Replacement

Click here to return to the 123WORDPRESS.COM HTML ...

Teach you how to use webpack to package and compile TypeScript code

TypeScript Bundling webpack integration Usually, ...

idea uses docker plug-in to achieve one-click automated deployment

Table of contents environment: 1. Docker enables ...

Specific use of CSS content attribute

The content attribute is generally used in the ::...