• React Native startReactApplication 方法简析


    React Native 启动流程简析 这篇文章里,我们梳理了 RN 的启动流程,最后的 startReactApplication 由于相对复杂且涉及到最终执行前端 js 的流程,我们单独将其提取出来,独立成文加以分析。

    首先来看 startReactApplication 的调用之处:

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

    可以看到是在 rootView 上调用 startReactApplication,入参为 instanceManager、appKey、mLaunchOptions

    顺着 startReactApplication 扒出其调用链:mReactInstanceManager.createReactContextInBackground() -> recreateReactContextInBackgroundInner() -> recreateReactContextInBackgroundFromBundleLoader() -> recreateReactContextInBackground() -> runCreateReactContextOnNewThread()

    recreateReactContextInBackgroundReactInstanceManager 中的方法,做了两件事:

    1. 创建 ReactContextInitParams 实例 initParams,如下,其入参 jsExecutorFactory 为创建 ReactInstanceManager 时传入。

      final ReactContextInitParams initParams =
          new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
      
    2. 调用 runCreateReactContextOnNewThread

    runCreateReactContextOnNewThreadReactInstanceManager 中的方法,主要做了两件事:

    1. 创建一个新的线程,并在新线程中通过 createReactContext 创建 ReactContext 上下文;
    2. 通过 setupReactContext 来设置上下文环境,并最终调用到 AppRegistry.js 启动App。

    createReactContext

    先看其调用的地方:

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

    其两个入参分别为 JsExecutorFactory 创建的 JavaScriptExecutor 实例,和 JsBundleLoader 实例。

    JavaScriptExecutor

    startReactApplication 第一个入参为 getReactNativeHost().getReactInstanceManager() 获取 ReactInstanceManager 实例。ReactInstanceManager 实例在 RN 应用中只有一个,先前在创建 MainActivity 时已创建。

    回顾 React Native 启动流程简析,在创建过程中实际上是调用下面的方法:

    ReactInstanceManager reactInstanceManager = builder.build()
    

    builderReactInstanceManagerBuilder,我们来到该类的 build 方法,发现其最终是执行 return new ReactInstanceManager(...),在构造参数中第 4 个参数即为:getDefaultJSExecutorFactory,来到其定义处:

      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) { /* ... */ }
    }
    

    也就是说在创建 ReactInstanceManagerBuilder 时我们就创建了 JSCExecutorFactory,并在随后调用其 create 方法创建 JSCExecutorJSCExecutorFactory 实现了 JavaScriptExecutorFactory 接口,其 create 方法如下,返回了 JSCExecutor 实例:

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

    再往下看 JSCExecutor 的定义,其继承自 JavaScriptExecutor 类:

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

    于是就很清楚了,createReactContext 第一个参数为 JSCExecutor 实例,是通过 SoLoader 加载的 C++ 模块。

    JsBundleLoader

    同样的,在 return new ReactInstanceManager(...),其构造参数中第 5 个参数为:JSBundleLoader.createAssetLoader(mApplication, mJSBundleAssetUrl, false)

    来到其定义之处,发现其返回了 JSBundleLoader 实例,并重写了其 loadScript 方法。

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

    在创建完 JSCExecutor 实例和 JSBundleLoader 实例后,正式进入到 createReactContext 方法。

    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;
    

    在这里面,首先创建了 reactContext,并通过 catalystInstanceBuilder 创建了 catalystInstance。接着通过 initializeWithInstance 方法将 reactContextcatalystInstance 关联起来,并进行了一系列的为 catalystInstance 初始化的工作。最后进入到方法 catalystInstance.runJSBundle() 中。

    initializeWithInstance

    通过调用 getUIQueueThreadgetNativeModulesQueueThreadgetJSQueueThread创建了3个线程队列,分别是 UI线程、NativeModules 线程,和 JS 线程。

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

    通过先前返回的 mJSBundleLoader 执行其 loadScript 方法:

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

    loadScriptFromAssets 方法在 CatalystInstanceImpl 中:

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

    这里的 assetURL 是在 createAssetLoader 创建 mJSBundleLoader 时传入,其赋值时机是在 reactInstanceManagerBuilder 实例中,由 reactNativeHost 实例的 createReactInstanceManager 方法。若 开发者在 MainApplication.java 中通过重写 getJSBundleFile 方法自定义了 assetURL 则使用该 url,否则使用系统默认,如:file://sdcard/myapp_cache/index.android.bundle

    jniLoadScriptFromAssets 方法为 C++ 侧定义的方法,用于读取 js 文件。为什么 Java 代码中可以直接调用 C++ 方法,这里还要打个问号,后续在分析 Java 与 C++ 通信及 Java 与 JS 通信时阐释。

    通过 createReactContext 创建了 reactContext,创建了 catalystInstance 实例,并将上述两者关联,接着通过 catalystInstance 读入 js 文件。接下来就进入到 setupReactContext 的环节。

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

    这里面做的事情如下:

    1. catalystInstance.initialize(): 所有原生模块的初始化
    2. attachRootViewToInstance(reactRoot): 绘制所有的 RootView 并添加到相应实例并设置相应的监听事件
    3. 创建 UI 模块、JS 模块和原生模块线程,并设置 JS 模块和原生模块所在线程的优先级

    总结本文

    从 createReactContext 和 setupReactContext 两个方法的源码出发,分析了 RN startReactApplication 方法的执行过程,其中:

    createReactContext 的主要作用是:创建 reactContext、创建 catalystInstance 实例,并将上述两者关联,接着通过 catalystInstance 读入 js 文件。

    setupReactContext的主要作用是:初始化所有原生模块,绘制所有 rootview,创建 UI 模块、JS 模块和原生模块线程,并设置优先级。

  • 相关阅读:
    CentOS7突然出现无法连接网络的情况--VM下
    设置Linux系统的LANG变量
    习题
    Linux目录路径知识
    Linux目录详细介绍
    regexp正则
    https://github.com/rwson/awesome-javascript-cn
    JavaScript资源大全中文版(Awesome最新版)
    关于 Chrome DevTools 的 25 个实用技巧
    从前端菜鸟到大神,看这一篇就够了
  • 原文地址:https://www.cnblogs.com/lilei94/p/15236674.html
Copyright © 2020-2023  润新知