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

MySQL Router implements MySQL read-write separation

Table of contents 1. Introduction 2. Configure My...

MYSQL slow query and log example explanation

1. Introduction By enabling the slow query log, M...

How to use MySQL stress testing tools

1. MySQL's own stress testing tool - Mysqlsla...

Javascript uses the integrity attribute for security verification

Table of contents 1. Import files using script ta...

Summary of Mysql update multi-table joint update method

Next, I will create two tables and execute a seri...

Detailed explanation of the usage of MySQL memory tables and temporary tables

Usage of MySQL memory tables and temporary tables...

Detailed installation and use of SSH in Ubuntu environment

SSH stands for Secure Shell, which is a secure tr...

In-depth explanation of currying of JS functions

Table of contents 1. Supplementary knowledge poin...

Detailed explanation of CSS counter related attributes learning

The CSS counter attribute is supported by almost ...

Vue uses three methods to refresh the page

When we are writing projects, we often encounter ...

MySQL uses frm files and ibd files to restore table data

Table of contents Introduction to frm files and i...

Use of filter() array filter in JS

Table of contents 1. Introduction 2. Introduction...

PHP-HTMLhtml important knowledge points notes (must read)

1. Use frameset, frame and iframe to realize mult...

Solve the margin: top collapse problem in CCS

The HTML structure is as follows: The CCS structu...