Análisis de código fuente nativo de React (1): inicialización del entorno y carga de paquetes

prefacio

El análisis del código fuente de React Native es una serie de artículos que incluyen tres partes, (1) inicialización y proceso de carga de paquetes (2) comunicación entre js y nativo (3) cómo se muestran los componentes de js en el lado nativo

Abrir una página de RN

Cree un proyecto de demostración a través de los documentos en el sitio web oficial de RN y vea cómo abrir la página de RN en él

rnpage proceso abierto.png

A partir del diagrama de flujo, la inicialización final del entorno RN y la apertura de la página RN se realizan en ReactRootView. En ReactRootView hay un

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

método, aquí está la entrada que queremos analizar

Análisis de proceso

Al analizar el código fuente, creo que hay tres etapas en el proceso de inicialización que son más importantes

(1) Creación de ReactInstanceManager

(2) Creación de ReactContext

(3) Asamblea de ReactContext

Creación de ReactInstanceManager

La creación de ReactInstanceManager en realidad se lleva a cabo en ReactNativeHost en el proyecto de plantilla.

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

Este ReactInstanceManager es una clase central de RN, que contiene toda la configuración, aquí nos enfocamos en

   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 representa la ruta del paquete que se va a cargar. Si la ruta está vacía o se coloca en el directorio de activos, mJSBundleAssetUrl se establecerá como el nombre del archivo del paquete, generalmente llamado (android.index.bundle), de lo contrario, se creará un JSBundleLoader, este cargador Se usa para cargar paquetes desde archivos, que se mencionarán más adelante en el ensamblado de ReactContext.

Creación de 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;
  }

Finalmente, llama a catalystInstance.runJSBundle() para cargar el paquete.

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

El mJSBundleLoader aquí es el mismo que el JSBundleLoader pasado cuando se creó ReactInstanceManager. No pasamos el JSBundleLoader en la demostración, y el JSBundleLoader predeterminado se usará aquí.

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

Puede ver que loadScipt en realidad usa el método loadScriptFromAssets de CatalystInstanceImpl, y CatalystInstanceImpl es la clase de implementación de CatalystInstance

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

Se puede ver que loadScriptFromAssets en CatalystInstanceImpl es una llamada para cargar el paquete a través del método C++, que no se describirá en detalle aquí. En este punto, se completa la creación de ReactContext y la carga de paquetes.

Asamblea de ReactContext

El montaje de ReactContext se realiza en 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);
              }
            }
          }
        });
      ... 
    }

En este punto, todo el proceso de inicialización del entorno ha terminado y también se devolverá la llamada al método onReactContextInitialized(), incluido

Uno es el método de inicialización de CatalystInstance, que se puede usar para inicializar NativeModules, luego encontrar el ReactRootView creado y ejecutar su 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();
    ...
  }

Finalmente, se llamará al método runApplication de ReactRootView


  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 llamará y declarará el método en la interfaz AppRegistry a través del proxy dinámico y, finalmente, runApplication asignará la función del mismo nombre llamada al código del lado 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);
  },

Ejecute el método renderApplication de AppRegistery para representar el componente.

Atentos a mi cuenta pública: el viejo arsénico en la patineta

Estoy participando en el reclutamiento del programa de firma de creadores de la Comunidad Tecnológica de Nuggets, haga clic en el enlace para registrarse y enviar .

Supongo que te gusta

Origin juejin.im/post/7116866204467200007
Recomendado
Clasificación