React Native source code analysis (1): environment initialization and Bundle loading

foreword

React Native source code analysis is a series of articles, including three parts, (1) initialization and bundle loading process (2) communication between js and native (3) how js components are displayed on the native side

Open an RN page

Create a demo project through the documents on the RN official website, and see how to open the RN page in it

rnpage open process.png

From the flow chart, the final initialization of the RN environment and the opening of the RN page are performed in ReactRootView. In ReactRootView there is a

startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle initialProperties)

method, here is the entry we want to analyze

Process analysis

By analyzing the source code, I think that there are three stages in the initialization process that are more important

(1) Creation of ReactInstanceManager

(2) Creation of ReactContext

(3) Assembly of ReactContext

Creation of ReactInstanceManager

The creation of ReactInstanceManager is actually carried out in ReactNativeHost in the template project.

 protected ReactInstanceManager createReactInstanceManager() {
   ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
   ReactInstanceManagerBuilder builder =
       ReactInstanceManager.builder()
           .setApplication(mApplication)
           .setJSMainModulePath(getJSMainModuleName())
           .setUseDeveloperSupport(getUseDeveloperSupport())
           .setDevSupportManagerFactory(getDevSupportManagerFactory())
           .setRequireActivity(getShouldRequireActivity())
           .setSurfaceDelegateFactory(getSurfaceDelegateFactory())
           .setRedBoxHandler(getRedBoxHandler())
           .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
           .setUIImplementationProvider(getUIImplementationProvider())
           .setJSIModulesPackage(getJSIModulePackage())
           .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
           .setReactPackageTurboModuleManagerDelegateBuilder(
               getReactPackageTurboModuleManagerDelegateBuilder());

   for (ReactPackage reactPackage : getPackages()) {
     builder.addPackage(reactPackage);
   }

   String jsBundleFile = getJSBundleFile();
   if (jsBundleFile != null) {
     builder.setJSBundleFile(jsBundleFile);
   } else {
     builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
   }
   ReactInstanceManager reactInstanceManager = builder.build();
   ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_END);
   return reactInstanceManager;
 }

This ReactInstanceManager is a core class of RN, which contains all the configuration, here we focus on

   String jsBundleFile = getJSBundleFile();
   if (jsBundleFile != null) {
     builder.setJSBundleFile(jsBundleFile);
   } else {
     builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
   }


   public ReactInstanceManagerBuilder setJSBundleFile(String jsBundleFile) {
   if (jsBundleFile.startsWith("assets://")) {
     mJSBundleAssetUrl = jsBundleFile;
     mJSBundleLoader = null;
     return this;
   }
   return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
 }


   public ReactInstanceManagerBuilder setBundleAssetName(String bundleAssetName) {
   mJSBundleAssetUrl = (bundleAssetName == null ? null : "assets://" + bundleAssetName);
   mJSBundleLoader = null;
   return this;
 }

jsBundleFile represents the bundle path to be loaded. If the path is empty or placed in the assets directory, then mJSBundleAssetUrl will be set as the bundle file name, generally named (android.index.bundle), otherwise a JSBundleLoader will be created, this loader Used to load bundles from files, which will be mentioned later in ReactContext assembly.

Creation of ReactContext

startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle initialProperties){
    ...
    mReactInstanceManager.createReactContextInBackground();
    ... 
}

ReactRootView的startReactApplication需要传入一个ReactInstanceManager,就是上一节构建的ReactInstanceManager。之后会调用ReactInstanceManager的createReactContextInBackground方法,顺着createReactContextInBackground方法逐级跟踪下去,发现最终会调用到ReactInstanceManager的runCreateReactContextOnNewThread(final ReactContextInitParams initParams)方法

  @ThreadConfined(UI)
  private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
    ...
    mCreateReactContextThread =
        new Thread(
            null,
            new Runnable() {
              @Override
              public void run() {
                ...
                try {
                  ...
                  //创建ReactContext 
                  reactApplicationContext =
                      createReactContext(
                          initParams.getJsExecutorFactory().create(),
                          initParams.getJsBundleLoader());
                } catch (Exception e) {
                  ...
                  return;
                }
                 ...
                  //装在ReactContext
                  Runnable setupReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          try {
                            setupReactContext(reactApplicationContext);
                          } catch (Exception e) {
                            ...
                          }
                        }
                      };

                  reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
                  ...
                } catch (Exception e) {
                  ...
                }
              }
            },
            "create_react_context");
    ...
    mCreateReactContextThread.start();
  }

可以看到在runCreateReactContextOnNewThread方法中会创建一个线程mCreateReactContextThread,在这个线程里会调用createReactContext进行ReactContext的创建,看一下createReactContext方法

/** @return instance of {@link ReactContext} configured a {@link CatalystInstance} set */
  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
    ...
    //1.创建ReactApplicationContext实例
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);

    NativeModuleCallExceptionHandler exceptionHandler =
        mNativeModuleCallExceptionHandler != null
            ? mNativeModuleCallExceptionHandler
            : mDevSupportManager;
    reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);
    //2.处理ReactPackage
    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

    CatalystInstanceImpl.Builder catalystInstanceBuilder =
        new CatalystInstanceImpl.Builder()
            .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
            .setJSExecutor(jsExecutor)
            .setRegistry(nativeModuleRegistry)
            .setJSBundleLoader(jsBundleLoader)
            .setNativeModuleCallExceptionHandler(exceptionHandler);

   ...
    try {
      //3.创建CatalystInstance实例 
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      ...
    }
    //4.ReactContext根据CatalystInstance进行初始化
    reactContext.initializeWithInstance(catalystInstance);

    ...
    //5执行jsbundle
    catalystInstance.runJSBundle();
    return reactContext;
  }

这个方法主要有五个部分,首先创建ReactApplicationContext实例,也是这个方法的返回值,然后去处理ReactPackage,这里会放到下期再说,先略过,接着是创建CatalystInstance实例,CatalystInstance主要用来初始化和js通信的桥,在native侧调用js侧方法,而且在构造函数里会创建native侧调用js方法的线程,js侧调用native方法的线程,以及UI线程,他们都是MessageQueueThreadImpl类的实例,这几个线程都有一个Looper。

  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry nativeModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
   ...
    //创建native线程,js线程以及ui线程
    mReactQueueConfiguration =
        ReactQueueConfigurationImpl.create(
            reactQueueConfigurationSpec, new NativeExceptionHandler());
    ...
    //初始化和js通信的桥
    initializeBridge(
        new BridgeCallback(this),
        jsExecutor,
        mReactQueueConfiguration.getJSQueueThread(),
        mNativeModulesQueueThread,
        mNativeModuleRegistry.getJavaModules(this),
        mNativeModuleRegistry.getCxxModules());
    ...
  }

接着调用reactContext.initializeWithInstance(catalystInstance);传入CatalystInstance到ReactContext进行配置,获取CatalystInstance中ReactQueueConfiguration的线程。

  /** Set and initialize CatalystInstance for this Context. This should be called exactly once. */
  public void initializeWithInstance(CatalystInstance catalystInstance) {
    ...
    ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
    initializeMessageQueueThreads(queueConfig);
  }

  /** Initialize message queue threads using a ReactQueueConfiguration. */
  public synchronized void initializeMessageQueueThreads(ReactQueueConfiguration queueConfig) {
    FLog.w(TAG, "initializeMessageQueueThreads() is called.");
    if (mUiMessageQueueThread != null
        || mNativeModulesMessageQueueThread != null
        || mJSMessageQueueThread != null) {
      throw new IllegalStateException("Message queue threads already initialized");
    }
    mUiMessageQueueThread = queueConfig.getUIQueueThread();
    mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
    mJSMessageQueueThread = queueConfig.getJSQueueThread();

    /** TODO(T85807990): Fail fast if any of the threads is null. */
    if (mUiMessageQueueThread == null) {
      throw new IllegalStateException("UI thread is null");
    }
    if (mNativeModulesMessageQueueThread == null) {
      throw new IllegalStateException("NativeModules thread is null");
    }
    if (mJSMessageQueueThread == null) {
      throw new IllegalStateException("JavaScript thread is null");
    }
    mIsInitialized = true;
  }

Finally, call catalystInstance.runJSBundle() to load the bundle

  @Override
  public void runJSBundle() {
    ...
    mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
    ...
  }

The mJSBundleLoader here is the same as the JSBundleLoader passed in when ReactInstanceManager was built. We did not pass in the JSBundleLoader in the demo, and the default JSBundleLoader will be used here.

  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;
      }
    };
  }

You can see that loadScipt actually uses the loadScriptFromAssets method of CatalystInstanceImpl, and CatalystInstanceImpl is the implementation class of CatalystInstance

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

It can be seen that the loadScriptFromAssets in CatalystInstanceImpl is a call to load the bundle through the C++ method, which will not be described in detail here. At this point, the creation of ReactContext and the loading of bundles are completed.

Assembly of ReactContext

The assembly of ReactContext is carried out in ReactInstanceManager

  private void setupReactContext(final ReactApplicationContext reactContext) {
      ...
      CatalystInstance catalystInstance =
          Assertions.assertNotNull(reactContext.getCatalystInstance());

      catalystInstance.initialize();

      mDevSupportManager.onNewReactContextCreated(reactContext);
      mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);

      ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
      for (ReactRoot reactRoot : mAttachedReactRoots) {
        if (reactRoot.getState().compareAndSet(ReactRoot.STATE_STOPPED, ReactRoot.STATE_STARTED)) {
          attachRootViewToInstance(reactRoot);
        }
      }
      ...
        UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            moveReactContextToCurrentLifecycleState();

            for (com.facebook.react.ReactInstanceEventListener listener : finalListeners) {
              // Sometimes this listener is null - probably due to race
              // condition between allocating listeners with a certain
              // size, and getting a `final` version of the array on
              // the following line.
              if (listener != null) {
                listener.onReactContextInitialized(reactContext);
              }
            }
          }
        });
      ... 
    }

At this point, the entire environment initialization process is over, and the onReactContextInitialized() method will also be called back, including

One is the initialize method of CatalystInstance, which can be used to initialize NativeModules, then find the created ReactRootView and execute its attachRootViewToInstance

  private void attachRootViewToInstance(final ReactRoot reactRoot) {
    FLog.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance");

    @Nullable
    UIManager uiManager =
        UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType());

    // If we can't get a UIManager something has probably gone horribly wrong
    if (uiManager == null) {
      throw new IllegalStateException(
          "Unable to attach a rootView to ReactInstance when UIManager is not properly"
              + " initialized.");
    }

    @Nullable Bundle initialProperties = reactRoot.getAppProperties();

    final int rootTag;
    ...
      rootTag =
          uiManager.addRootView(
              reactRoot.getRootViewGroup(),
              initialProperties == null
                  ? new WritableNativeMap()
                  : Arguments.fromBundle(initialProperties),
              reactRoot.getInitialUITemplate());
      reactRoot.setRootViewTag(rootTag);
      reactRoot.runApplication();
    ...
  }

Finally, the runApplication method of ReactRootView will be called


  public void runApplication() {
    try {
      if (mReactInstanceManager == null || !mIsAttachedToInstance) {
        return;
      }

      ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
      if (reactContext == null) {
        return;
      }

      CatalystInstance catalystInstance = reactContext.getCatalystInstance();
      String jsAppModuleName = getJSModuleName();

      if (mWasMeasured) {
        updateRootLayoutSpecs(true, mWidthMeasureSpec, mHeightMeasureSpec);
      }

      WritableNativeMap appParams = new WritableNativeMap();
      appParams.putDouble("rootTag", getRootViewTag());
      @Nullable Bundle appProperties = getAppProperties();
      if (appProperties != null) {
        appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
      }

      mShouldLogContentAppeared = true;

      catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
  }

CatalystInstance will call and declare the method in the AppRegistry interface through the dynamic proxy, and finally the runApplication will map the function of the same name called to the js side code

 // AppRegistry.js
  runApplication(
    appKey: string,
    appParameters: any,
    displayMode?: number,
  ): void {
    if (appKey !== 'LogBox') {
      const logParams = __DEV__
        ? '" with ' + JSON.stringify(appParameters)
        : '';
      const msg = 'Running "' + appKey + logParams;
      infoLog(msg);
      BugReporting.addSource(
        'AppRegistry.runApplication' + runCount++,
        () => msg,
      );
    }
    invariant(
      runnables[appKey] && runnables[appKey].run,
      `"${appKey}" has not been registered. This can happen if:\n` +
        '* Metro (the local dev server) is run from the wrong folder. ' +
        'Check if Metro is running, stop it and restart it in the current project.\n' +
        "* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.",
    );

    SceneTracker.setActiveScene({name: appKey});
    runnables[appKey].run(appParameters, displayMode);
  },

Execute the renderApplication method of AppRegistery to render the component.

Pay attention to my public account: the old arsenic on the skateboard

I am participating in the recruitment of the creator signing program of the Nuggets Technology Community, click the link to register and submit .

Guess you like

Origin juejin.im/post/7116866204467200007