React Nativeソースコード分析(1):環境の初期化とバンドルのロード

序文

React Nativeソースコード分析は、(1)初期化とバンドルロードプロセス(2)jsとネイティブ間の通信(3)jsコンポーネントがネイティブ側でどのように表示されるかという3つの部分を含む一連の記事です。

RNページを開く

RN公式ウェブサイトのドキュメントからデモプロジェクトを作成し、RNページを開く方法を確認してください

rnpage open process.png

フローチャートから、RN環境の最終的な初期化とRNページのオープンがReactRootViewで実行されます。ReactRootViewには

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

メソッド、これが分析したいエントリです

プロセス分析

ソースコードを分析すると、初期化プロセスには、より重要な3つの段階があると思います。

(1)ReactInstanceManagerの作成

(2)ReactContextの作成

(3)ReactContextのアセンブリ

ReactInstanceManagerの作成

ReactInstanceManagerの作成は、実際にはテンプレートプロジェクトのReactNativeHostで実行されます。

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

このReactInstanceManagerはRNのコアクラスであり、すべての構成が含まれています。ここでは、以下に焦点を当てます。

   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は、ロードされるバンドルパスを表します。パスが空であるか、アセットディレクトリに配置されている場合、mJSBundleAssetUrlはバンドルファイル名として設定され、通常は(android.index.bundle)という名前になります。それ以外の場合は、JSBundleLoaderが作成されます。ローダーファイルからバンドルをロードするために使用されます。これについては、後でReactContextアセンブリで説明します。

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

最後に、catalystInstance.runJSBundle()を呼び出してバンドルをロードします

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

ここでのmJSBundleLoaderは、ReactInstanceManagerがビルドされたときに渡されたJSBundleLoaderと同じです。デモではJSBundleLoaderを渡さなかったため、デフォルトのJSBundleLoaderがここで使用されます。

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

loadSciptが実際にCatalystInstanceImplのloadScriptFromAssetsメソッドを使用し、CatalystInstanceImplがCatalystInstanceの実装クラスであることがわかります。

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

CatalystInstanceImplのloadScriptFromAssetsは、C ++メソッドを介してバンドルをロードするための呼び出しであることがわかります。これについては、ここでは詳しく説明しません。この時点で、ReactContextの作成とバンドルのロードが完了します。

ReactContextのアセンブリ

ReactContextのアセンブリは、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);
              }
            }
          }
        });
      ... 
    }

この時点で、環境初期化プロセス全体が終了し、onReactContextInitialized()メソッドもコールバックされます。

1つはCatalystInstanceの初期化メソッドで、NativeModulesを初期化してから、作成されたReactRootViewを見つけて、その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();
    ...
  }

最後に、ReactRootViewのrunApplicationメソッドが呼び出されます


  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は、動的プロキシを介してAppRegistryインターフェイスでメソッドを呼び出して宣言し、最後にrunApplicationは呼び出された同じ名前の関数をjsサイドコードにマップします。

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

AppRegisteryのrenderApplicationメソッドを実行して、コンポーネントをレンダリングします。

私の公開アカウントに注意してください:スケートボードの古いヒ素

ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。

おすすめ

転載: juejin.im/post/7116866204467200007