美文网首页
从 ReactRootView 角度理解React-Native

从 ReactRootView 角度理解React-Native

作者: 杨体仁 | 来源:发表于2021-02-27 01:40 被阅读0次

    众所周知,React-Native 是一款优秀的 JavaScript 框架,它将 React 和原生相结合,将 React 组件渲染为原生平台的组件,这意味着性能和原生控件几无二别。它可以用JSX,混编js、css、html,这大大提高了开发的效率,开发者只关心如何用 JavaScript 构造页面。而它的高效之处在于独创的 Virtual DOM,Virtual DOM是存在于内存中的 JavaScript 对象,它和 DOM 是一一对应的关系,得益于高效的 DOM-diff 算法,当界面发生变化的时候,Virtual DOM 会高效的更改 DOM 从而避免了频繁的绘制,提高了性能。

    但是,在现实的使用场景之中,我们常常混合开发,而不会纯粹的使用 React-Native,这是由于,虽然 FaceBook 官方提供了大量的组件,但是毕竟还是有很多业务上的或者是代码逻辑上的需求是无法满足的,这就需要两端另外去开发对应的组件,反而增大了开发成本。除此之外,作为至关重要的列表,FlatList 的表现差强人意,只能够展现相对简单的列表,而图片过多,或者内容相对复杂,就会导致明显的掉帧和卡顿。

    下面将从 ReactRootView 的角度来理解 React-Native 的加载过程。
    我们在使用一个 React-Native 页面的时候,有两种实现方式,一种是继承官方的 ReactActivity ,另一种是把 ReactRootView 添加进布局。
    第一种方式相对简单。这两种发方法大同小异,只不过第二种方法相对灵活,但是需要自己去挂载和卸载组件。当我们跟踪 ReactActivityDelegate 的代码,会发现实现挂载的调用最终走向了这个方法:

    public void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    mReactRootView = createRootView();
    mReactRootView.startReactApplication(
        getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);}    
    

    mReactRootView.startReactApplication 方法即是用来启动JS应用,并且渲染由js组件。继续往下看:

    public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle initialProperties,
      @Nullable String initialUITemplate) {
    try {
      UiThreadUtil.assertOnUiThread();
      mReactInstanceManager = reactInstanceManager;
      mJSModuleName = moduleName;
      mAppProperties = initialProperties;
      mInitialUITemplate = initialUITemplate;
      mReactInstanceManager.createReactContextInBackground();
      attachToReactInstanceManager();
    
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
    }
    

    这里去掉了冗余的代码,只留下关键性代码,createReactContextInBackground() 方法是在子线程中创建 ReactContext ,之后会调用 recreateReactContextInBackgroundInner() 方法,这里需要用到强大的 ReactInstanceManager 类,ReactInstanceManager 是一个比较核心的类,它管理着 ReactRootView 的生命周期,管理着 js 的加载,管理着Native和js 的交互,管理着初始化的参数等等。

    这里我们只关心 js 的加载和View 的挂载。一路追踪下去,执行的是
    recreateReactContextInBackgroundFromBundleLoader()方法,这段关键代码是: recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
    而 mJavaScriptExecutorFactory 和 mBundleLoader 是在 类,ReactInstanceManager 初始化的时候已经准备就绪了,这两个类就是用来执行js文件的,JavaScriptExecutor 的作用是调用Native方法initHybrid()初始化C++层RN与JSC通信的框架,JSBundleLoader 是通过不同的场景去创建不同的加载器,用以加载js文件,这两个类会封装为 ReactContextInitParams 传递给核心的方法: runCreateReactContextOnNewThread(),在这这个线程中有两个关键的部分,一个是创建 ReactContext, 一个是 setupReactContext。
    首先看下createReactContext():

     private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
    FLog.d(ReactConstants.TAG, "ReactInstanceManager.createReactContext()");
    ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    
    NativeModuleCallExceptionHandler exceptionHandler =
        mNativeModuleCallExceptionHandler != null
            ? mNativeModuleCallExceptionHandler
            : mDevSupportManager;
    reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);
    
    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
    
    
    CatalystInstanceImpl.Builder catalystInstanceBuilder =
        new CatalystInstanceImpl.Builder()
            .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
            .setJSExecutor(jsExecutor)
            .setRegistry(nativeModuleRegistry)
            .setJSBundleLoader(jsBundleLoader)
            .setNativeModuleCallExceptionHandler(exceptionHandler);
    
    ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
    // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    final CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
    }
    
    reactContext.initializeWithInstance(catalystInstance);
    
    // TODO(T46487253): Remove after task is closed
    FLog.e(
        ReactConstants.TAG,
        "ReactInstanceManager.createReactContext: mJSIModulePackage "
            + (mJSIModulePackage != null ? "not null" : "null"));
    
    if (mJSIModulePackage != null) {
      catalystInstance.addJSIModules(
          mJSIModulePackage.getJSIModules(
              reactContext, catalystInstance.getJavaScriptContextHolder()));
    
      // TODO(T46487253): Remove after task is closed
      FLog.e(
          ReactConstants.TAG,
          "ReactInstanceManager.createReactContext: ReactFeatureFlags.useTurboModules == "
              + (ReactFeatureFlags.useTurboModules == false ? "false" : "true"));
    
      if (ReactFeatureFlags.useTurboModules) {
        JSIModule turboModuleManager =
            catalystInstance.getJSIModule(JSIModuleType.TurboModuleManager);
    
        // TODO(T46487253): Remove after task is closed
        FLog.e(
            ReactConstants.TAG,
            "ReactInstanceManager.createReactContext: TurboModuleManager "
                + (turboModuleManager == null ? "not created" : "created"));
    
        catalystInstance.setTurboModuleManager(turboModuleManager);
    
        TurboModuleRegistry registry = (TurboModuleRegistry) turboModuleManager;
    
        // Eagerly initialize TurboModules
        for (String moduleName : registry.getEagerInitModuleNames()) {
          registry.getModule(moduleName);
        }
      }
    }
    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }
    if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
      catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
    }
    ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
    catalystInstance.runJSBundle();
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    
    return reactContext;
    }
    

    在这个方法中,主要是 ReactContet 的初始化,而ReactContet 仅仅继承自 ContextWrapper 而已,但是经过官方的扩展,它承担了 JS 和原生沟通的桥梁。
    NativeModuleRegistry 通过 processPackages()方法 整合了项目中所有的的封装组件或者是交互类,即 NativeModule , 按照官方的写法,封装成为 ReactPackage, 交由 CatalystInstance 去绑定。

    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
    

    而原生和 js 的交互正是由此而来,比如这样的一段代码:

    const TextInANest = () => {
        return (
    <Text style={styles.baseText}>
      <Text style={styles.titleText} onPress={onPressTitle}>
        {titleText}
        {"\n"}
        {"\n"}
      </Text>
      <Text numberOfLines={5}>{bodyText}</Text>
    </Text>
     );
    };
    

    Text 组件拥有自己的属性和方法,我们知道,所有的 React 组件最终会映射为原生组件,那么在原生和 js 之间必然维护了一个映射关系,当我们去查看 ReactTextViewManager 和它的父类 ReactTextAnchorViewManager 就会发现几乎所有的属性。首先,React Native将代码由JSX转化为JS组件,启动过程中利用instantiateReactComponent将ReactElement转化为复合组件ReactCompositeComponent与元组件ReactNativeBaseComponent,利用
    ReactReconciler对他们进行渲染。UIManager.js利用C++层的Instance.cpp将UI信息传递给UIManagerModule.java,并利用UIManagerModule.java构建UI。UIManagerModule.java接收到UI信息后,将UI的操作封装成对应的Action,放在队列中等待执行。各种UI的操作,例如创建、销毁、更新等便在队列里完成,UI最终得以渲染在屏幕上。而这个 ReactTextViewManager 最终是通过 ReactPackage 传递给NativeModuleRegistry 的作用就是把这些封装的View或者交互的 Moudules 进行注册,销毁,刷新等工作,与之相对应的js管理类是 JavaScriptModuleRegistry,这样一来,就为下一步二者之间的交互搭建好基础。

    CatalystInstanceImpl 类在初始化的过程中,通过 ReactInstanceManager 把相应的参数带了进来,并初始化出三个 MessageQueueThread ,MessageQueueThread 只是一个接口,方便接受 Runable 进行调用,之后通过 CatalystInstance 的 runJSBundle() 方法加载 js 文件,在 CatalystInstanceImpl 实现类中调用 mJSBundleLoader.loadScript(CatalystInstanceImpl.this) 方法,

    public void initializeMessageQueueThreads(ReactQueueConfiguration queueConfig) {
    if (mUiMessageQueueThread != null
        || mNativeModulesMessageQueueThread != null
        || mJSMessageQueueThread != null) {
      throw new IllegalStateException("Message queue threads already initialized");
    }
    mUiMessageQueueThread = queueConfig.getUIQueueThread();
    mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
    mJSMessageQueueThread = queueConfig.getJSQueueThread(); }     
    

    这里初始化三个调度Runable 的类,分别是本地线程,js线程,ui线程,以方便交互。 至此,js已经加载,而且Native 和js 的桥梁也搭建完成。
    回头看 setupReactContext 方法,它的主要作用是管理ReactRootView 的生命周期,在一个项目中,可能存在多个 ReactRootView,这里通过遍历的方式,执行 attachRootViewToInstance(reactRoot)方法,同时在ReactContext 创建完成的回调中,初始化一些插件,主要内容在 ReactNativeFlipper 当中,暂不赘述,关键的代码在 attachRootViewToInstance 当中:

    private void attachRootViewToInstance(final ReactRoot reactRoot) {
    // TODO: downgrade back to FLog.d once T62192299 is resolved.
    FLog.e(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 =
        uiManager.addRootView(
            reactRoot.getRootViewGroup(),
            initialProperties == null
                ? new WritableNativeMap()
                : Arguments.fromBundle(initialProperties),
            reactRoot.getInitialUITemplate());
    reactRoot.setRootViewTag(rootTag);
    if (reactRoot.getUIManagerType() == FABRIC) {
      // Fabric requires to call updateRootLayoutSpecs before starting JS Application,
      // this ensures the root will hace the correct pointScaleFactor.
      uiManager.updateRootLayoutSpecs(
          rootTag, reactRoot.getWidthMeasureSpec(), reactRoot.getHeightMeasureSpec());
      reactRoot.setShouldLogContentAppeared(true);
    } else {
      reactRoot.runApplication();
    }
    Systrace.beginAsyncSection(
        TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
    UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            Systrace.endAsyncSection(
                TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
            reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE);
          }
        });
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
    
    其主要的作用就是通过 UIManager 去渲染控件,至此完整的启动流程已经结束。
    

    总结:

    ReactContext:ReactContext继承于ContextWrapper,是ReactNative应用的上下文,通过getContext()去获得,通过它可以访问ReactNative核心类的实现。

    ReactInstanceManager:ReactInstanceManager是ReactNative应用总的管理类,创建ReactContext、CatalystInstance等类,解析ReactPackage生成映射表,并且配合ReactRootView管理View的创建与生命周期等功能。

    CatalystInstance:CatalystInstance是ReactNative应用Java层、C++层、JS层通信总管理类,总管Java层、JS层核心Module映射表与回调,三端通信的入口与桥梁。

    JavaScriptModule:JavaScriptModule是JS Module,负责JS到Java的映射调用格式声明,由CatalystInstance统一管理。

    NativeModule:NativeModule是Java Module,负责Java到Js的映射调用格式声明,由CatalystInstance统一管理。

    JavascriptModuleRegistry:JavascriptModuleRegistry是JS Module映射表,NativeModuleRegistry是Java Module映射表。

    UIManager:主要处理UI的渲染,JS层通过C++层把创建View的请求发送给Java层的UIManagerModule。UIManagerModule通过UIImplentation对操作请求进行包装。
    包装后的操作请求被发送到View处理队列UIViewOperationQueue队列中等待处理。实际处理View时,根据class name查询对应的ViewNManager,然后调用原生View的方法对View进行相应的操作。

    相关文章

      网友评论

          本文标题:从 ReactRootView 角度理解React-Native

          本文链接:https://www.haomeiwen.com/subject/lizefltx.html