RN 原理

啟動流程

RN的頁面也是依托Activity,React Native框架里有一個ReactActivity归榕,它就是我們RN頁面的容器。
ReactActivity里有個ReactRootView绩郎,正如它的名字那樣叉寂,它就是 ReactActivity的root View萍启,最終渲染出來的view都會添加到這個ReactRootView上。
ReactRootView調(diào)用自己的startReactApplication()方法啟動了整個RN頁面,在啟動的過程中先去創(chuàng)建頁面上下文ReactContext勘纯,然后再去加載局服、執(zhí)行并將JavaScript映射成Native Widget,最終一個RN頁面就顯示在了用戶面前驳遵。

整個RN頁面的啟動流程圖如下所示


啟動流程圖
  1. 創(chuàng)建ReactRootView 過程
public class MainActivity extends ReactActivity {}
public abstract class ReactActivity extends AppCompatActivity
    implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
  private final ReactActivityDelegate mDelegate;

  protected ReactActivity() {
    mDelegate = createReactActivityDelegate();
  }
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName());
  }
}
public class ReactActivityDelegate {
  public ReactActivityDelegate(ReactActivity activity, @Nullable String mainComponentName) {
    mActivity = activity;
    mMainComponentName = mainComponentName;
  }
  protected void onCreate(Bundle savedInstanceState) {
    if (mMainComponentName != null) {
      loadApp(mMainComponentName);
    }
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }
  protected void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    mReactRootView = createRootView();
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());
    getPlainActivity().setContentView(mReactRootView);
  }
}
  1. 創(chuàng)建 ReactInstanceManager
  public ReactInstanceManager getReactInstanceManager() {
    if (mReactInstanceManager == null) {
      ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_START);
      mReactInstanceManager = createReactInstanceManager();
      ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_END);
    }
    return mReactInstanceManager;
  }

  protected ReactInstanceManager createReactInstanceManager() {
    ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
      // 應(yīng)用上下文
      .setApplication(mApplication)
     //JSMainModuleP相當(dāng)于應(yīng)用首頁的js Bundle淫奔,可以傳遞url從服務(wù)器拉取js Bundle
     //當(dāng)然這個只在dev模式下可以使用
      .setJSMainModulePath(getJSMainModuleName())
      //是否開啟dev模式
      .setUseDeveloperSupport(getUseDeveloperSupport())
       //紅盒的回調(diào)
      .setRedBoxHandler(getRedBoxHandler())
      //JS執(zhí)行器
      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
       //自定義UI實現(xiàn)機制,這個我們一般用不到
      .setUIImplementationProvider(getUIImplementationProvider())
      .setJSIModulesPackage(getJSIModulePackage())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
     //添加我們外面設(shè)置的Package
    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }
     //獲取js Bundle的加載路徑
    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;
  }
  1. 創(chuàng)建 ReactContext
    ReactContext

    我們看到從最初的 startReactApplication 最終會調(diào)用到這里
public class ReactInstanceManager {
    
    private ReactApplicationContext createReactContext(
         JavaScriptExecutor jsExecutor,
         JSBundleLoader jsBundleLoader) {
       Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContext()");
       ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
       //ReactApplicationContext是ReactContext的包裝類堤结。
       final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
   
       //debug模式里開啟異常處理器唆迁,就是我們開發(fā)中見到的調(diào)試工具(紅色錯誤框等)
       if (mUseDeveloperSupport) {
         reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
       }
   
       //創(chuàng)建JavaModule注冊表
       NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
   
       NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
         ? mNativeModuleCallExceptionHandler
         : mDevSupportManager;
       
       //創(chuàng)建CatalystInstanceImpl的Builder,它是三端通信的管理類
       CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
         .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
         //JS執(zhí)行器
         .setJSExecutor(jsExecutor)
         //Java Module注冊表
         .setRegistry(nativeModuleRegistry)
         //JS Bundle加載器
         .setJSBundleLoader(jsBundleLoader)
         //Java Exception處理器
         .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;
       //構(gòu)建CatalystInstance實例
       try {
         catalystInstance = catalystInstanceBuilder.build();
       } finally {
         Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
         ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
       }
   
       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);
       //開啟加載執(zhí)行JS Bundle
       catalystInstance.runJSBundle();
       //關(guān)聯(lián)catalystInstance與reactContext
       reactContext.initializeWithInstance(catalystInstance);
   
       return reactContext;
     } 
}

在這個方法里完成了RN頁面上下文ReactContext的創(chuàng)建竞穷,我們先來看看這個方法的兩個入?yún)ⅲ?/p>

  • jsExecutorJSCJavaScriptExecutor繼承于JavaScriptExecutor唐责,當(dāng)該類被加載時,它會自動去加載"reactnativejnifb.so"庫来庭,并會調(diào)用Native方法initHybrid()初始化C++層RN與JSC通信的框架妒蔚。
  • jsBundleLoader:緩存了JSBundle的信息,封裝了上層加載JSBundle的相關(guān)接口月弛,CatalystInstanceImpl 實現(xiàn)了 JSBundleLoaderDelegate接口,可以間接調(diào)用ReactBridge的 native 方法去加載JS文件科盛,不同的場景會創(chuàng)建不同的加載器帽衙,具體可以查看類JSBundleLoader。

可以看到在ReactContext創(chuàng)建的過程中贞绵,主要做了以下幾件事情:

  1. 構(gòu)建ReactApplicationContext對象厉萝,ReactApplicationContext是ReactContext的包裝類。
  2. 利用jsExecutor榨崩、nativeModuleRegistry谴垫、jsBundleLoaderexceptionHandler等參數(shù)構(gòu)建CatalystInstance實例母蛛,作為以為三端通信的中樞翩剪。
  3. 調(diào)用CatalystInstance.runJSBundle()開始加載執(zhí)行JS。

另一個重要的角色CatalystInstance出現(xiàn)了彩郊,前面我們也說過它是三端通信的中樞前弯。關(guān)于通信的具體實現(xiàn)我們會在接下來的通信機制小節(jié)來講述,我們先來接著看JS的加載過程秫逝。

加載 JS Bundle

首先是CatalystInstanceImpl

  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry nativeModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl");
    mHybridData = initHybrid();
    //創(chuàng)建三大線程:UI線程恕出、Native線程與JS線程
    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        reactQueueConfigurationSpec,
        new NativeExceptionHandler());
    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
    mNativeModuleRegistry = nativeModuleRegistry;
    //創(chuàng)建JS Module注冊表實例,這個在以前的代碼版本中是在上面的createReactContext()方法中創(chuàng)建的
    mJSModuleRegistry = new JavaScriptModuleRegistry();
    mJSBundleLoader = jsBundleLoader;
    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
    mTraceListener = new JSProfilerTraceListener(this);
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);

    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge before initializeBridge");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeCxxBridge");
    //在C++層初始化通信橋
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mNativeModulesQueueThread,
      mNativeModuleRegistry.getJavaModules(this),
      mNativeModuleRegistry.getCxxModules());
    Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);

    mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
  }

ReactQueueConfigurationSpec用來創(chuàng)建ReactQueueConfiguration的實例违帆,ReactQueueConfiguration
同樣是個接口浙巫,它的實現(xiàn)類是ReactQueueConfigurationImplReactQueueConfiguration的定義如下

public interface ReactQueueConfiguration {
  //UI線程
  MessageQueueThread getUIQueueThread();
  //Native線程
  MessageQueueThread getNativeModulesQueueThread();
  //JS線程
  MessageQueueThread getJSQueueThread();
  void destroy();
}

JS Bundle的加載實際上是指C++層完成的刷后,我們看一下序列圖

runJSBundle

JSBundle 有三種加載方式:setSourceURLs, loadScriptFromAssets, loadScriptFromFile
而真正執(zhí)行JS的地方是 JSCExector.cpp 的 loadApplicationScript()方法

  void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
      SystraceSection s("JSCExecutor::loadApplicationScript",
                        "sourceURL", sourceURL);
        ...
        switch (jsStatus) {
          case JSLoadSourceIsCompiled:
            if (!bcSourceCode) {
              throw std::runtime_error("Unexpected error opening compiled bundle");
            }
            //調(diào)用JavaScriptCore里的方法驗證JS是否有效的畴,并解釋執(zhí)行
            evaluateSourceCode(m_context, bcSourceCode, jsSourceURL);

            flush();

            ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
            ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
            return;

          case JSLoadSourceErrorVersionMismatch:
            throw RecoverableError(explainLoadSourceStatus(jsStatus));

          case JSLoadSourceErrorOnRead:
          case JSLoadSourceIsNotCompiled:
            // Not bytecode, fall through.
            break;
        }
      }

    void JSCExecutor::flush() {
      SystraceSection s("JSCExecutor::flush");

      if (m_flushedQueueJS) {
          //調(diào)用MessageQueue.js的flushedQueue()方法
        callNativeModules(m_flushedQueueJS->callAsFunction({}));
        return;
      }

      // When a native module is called from JS, BatchedBridge.enqueueNativeCall()
      // is invoked.  For that to work, require('BatchedBridge') has to be called,
      // and when that happens, __fbBatchedBridge is set as a side effect.
      auto global = Object::getGlobalObject(m_context);
      auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
      // So here, if __fbBatchedBridge doesn't exist, then we know no native calls
      // have happened, and we were able to determine this without forcing
      // BatchedBridge to be loaded as a side effect.
      if (!batchedBridgeValue.isUndefined()) {
        // If calls were made, we bind to the JS bridge methods, and use them to
        // get the pending queue of native calls.
        bindBridge();
        callNativeModules(m_flushedQueueJS->callAsFunction({}));
      } else if (m_delegate) {
        // If we have a delegate, we need to call it; we pass a null list to
        // callNativeModules, since we know there are no native calls, without
        // calling into JS again.  If no calls were made and there's no delegate,
        // nothing happens, which is correct.
        callNativeModules(Value::makeNull(m_context));
      }
    }

可以看到這個方法主要是調(diào)用JavaScriptCore里的evaluateSourceCode()方法驗證JS是否有效廉油,并解釋執(zhí)行。然后再調(diào)用flush()方法調(diào)用JS層里的方法執(zhí)行JS Bundle苗傅。

上面提到flush()方法調(diào)用MessageQueue.js的flushedQueue()方法抒线,這是如何做到的呢?
事實上這是由bindBridge()方法來完成的渣慕,它從__fbBatchedBridge 對象中取出MessageQueue.js里的四個方法

callFunctionReturnFlushedQueue()
invokeCallbackAndReturnFlushedQueue()
flushedQueue()
callFunctionReturnResultAndFlushedQueue()

并分別存在四個C++變量中:

m_callFunctionReturnFlushedQueueJS
m_invokeCallbackAndReturnFlushedQueueJS
m_flushedQueueJS
m_callFunctionReturnResultAndFlushedQueueJS

這樣C++就可以調(diào)用這四個JS方法嘶炭。

void JSCExecutor::bindBridge() throw(JSException) {
  SystraceSection s("JSCExecutor::bindBridge");
  std::call_once(m_bindFlag, [this] {
    auto global = Object::getGlobalObject(m_context);
    auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      auto requireBatchedBridge = global.getProperty("__fbRequireBatchedBridge");
      if (!requireBatchedBridge.isUndefined()) {
        batchedBridgeValue = requireBatchedBridge.asObject().callAsFunction({});
      }
      if (batchedBridgeValue.isUndefined()) {
        throw JSException("Could not get BatchedBridge, make sure your bundle is packaged correctly");
      }
    }

    auto batchedBridge = batchedBridgeValue.asObject();
    m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
    m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject();
    m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
    m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();
  });
}

綁定ReactContext與ReactRootView

JS Bundle加載完成以后,前面說的createReactContext()就執(zhí)行完成了逊桦,然后開始執(zhí)行setupReacContext()方法眨猎,綁定ReactContextReactRootView。 我們來看一下它的實現(xiàn)强经。

public class ReactInstanceManager {
    
    private void setupReactContext(final ReactApplicationContext reactContext) {
        //...
        
        //執(zhí)行Java Module的初始化
        catalystInstance.initialize();
        //通知ReactContext已經(jīng)被創(chuàng)建愛女
        mDevSupportManager.onNewReactContextCreated(reactContext);
        //內(nèi)存狀態(tài)回調(diào)設(shè)置
        mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
        //復(fù)位生命周期
        moveReactContextToCurrentLifecycleState();
    
        ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
        synchronized (mAttachedRootViews) {
          //將所有的ReactRootView與catalystInstance進行綁定
          for (ReactRootView rootView : mAttachedRootViews) {
            attachRootViewToInstance(rootView, catalystInstance);
          }
        }
        //...
      }
    
      private void attachRootViewToInstance(
          final ReactRootView rootView,
          CatalystInstance catalystInstance) {
        //...
        UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
        //將ReactRootView作為根布局
        final int rootTag = uiManagerModule.addRootView(rootView);
        rootView.setRootViewTag(rootTag);
        //執(zhí)行JS的入口bundle頁面
        rootView.invokeJSEntryPoint();
        //...
      }
}

JS的頁面入口我們可以設(shè)置mJSEntryPoint來自定義入口睡陪,如果不設(shè)置則是默認的入口AppRegistry。

  private void defaultJSEntryPoint() {
      //...
      try {
        //...
        String jsAppModuleName = getJSModuleName();
        catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
  }

這里的調(diào)用方式實際上就是原生調(diào)用JS的方法匿情,它調(diào)用的正是我們很熟悉的AppRegistry.js兰迫,AppRegistry.js調(diào)用runApplication()開始執(zhí)行JS頁面的渲染,最終轉(zhuǎn)換為Native UI顯示在手機上炬称。
到此為止汁果,整個RN頁面的啟動流程就分析完了,我們接著來看看RN頁面是如何渲染最終顯示在手機上的玲躯。

渲染

RN頁面的入口一般是AppRegistry.js据德,先看一下RN頁面的渲染流程序列圖,如下所示:


整體渲染過程

大體過程:

  1. React Native將代碼由JSX轉(zhuǎn)化為JS組件跷车,啟動過程中利用instantiateReactComponentReactElement轉(zhuǎn)化為復(fù)合組件ReactCompositeComponent與元組件ReactNativeBaseComponent棘利,利用ReactReconciler對他們進行渲染。
  2. UIManager.js利用C++層的Instance.cpp將UI信息傳遞給UIManagerModule.java朽缴,并利用UIManagerModule.java構(gòu)建UI善玫。
  3. UIManagerModule.java接收到UI信息后,將UI的操作封裝成對應(yīng)的Action不铆,放在隊列中等待執(zhí)行蝌焚。各種UI的操作,例如創(chuàng)建誓斥、銷毀只洒、更新等便在隊列里完成,UI最終得以渲染在屏幕上劳坑。

JavaScript層組件渲染

JavaScript層組件渲染
  • AppRegistry.registerComponent用來注冊組件毕谴,在該方法內(nèi)它會調(diào)用AppRegistry.runApplication()來啟動js的渲染流程。
  • AppRegistry.runApplication()會將傳入的Component轉(zhuǎn)換成ReactElement,并在外面包裹一層AppContaniner涝开,AppContaniner主要用來提供一些debug工具(例如:紅盒)
// ReactNativeMount.js
  renderComponent: function(
    nextElement: ReactElement<*>,
    containerTag: number,
    callback?: ?(() => void)
  ): ?ReactComponent<any, any, any> {
  
    //將RectElement使用相同的TopLevelWrapper進行包裹
    var nextWrappedElement = React.createElement(
      TopLevelWrapper,
      { child: nextElement }
    );

    var topRootNodeID = containerTag;
    var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID];
    if (prevComponent) {
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props.child;
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement, emptyObject);
        if (callback) {
          ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
        }
        return prevComponent;
      } else {
        ReactNativeMount.unmountComponentAtNode(containerTag);
      }
    }

    if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
      console.error('You cannot render into anything but a top root');
      return null;
    }

    ReactNativeTagHandles.assertRootTag(containerTag);

    //檢查之前的節(jié)點是否已經(jīng)mount到目標節(jié)點上循帐,如果有則進行比較處理
    var instance = instantiateReactComponent(nextWrappedElement, false);
    ReactNativeMount._instancesByContainerID[containerTag] = instance;

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.

    //將mount任務(wù)提交給回調(diào)Queue,最終會調(diào)用ReactReconciler.mountComponent()
    ReactUpdates.batchedUpdates(
      batchedMountComponentIntoNode,
      instance,
      containerTag
    );
    var component = instance.getPublicInstance();
    if (callback) {
      callback.call(component);
    }
    return component;
  },

該方法主要做了以下事情:

  1. 將傳入的RectElement使用相同的TopLevelWrapper進行包裹舀武,生成nextWrappedElement拄养。
  2. 檢查之前的節(jié)點是否已經(jīng)mount到目標節(jié)點上,如果有則進行比較處理银舱,將上一步生成的nextWrappedElement 傳入 instantiateReactComponent(nextWrappedElement, false) 方法瘪匿。
  3. 將mount任務(wù)提交給回調(diào)Queue,最終會調(diào)用ReactReconciler.mountComponent()寻馏,ReactReconciler.mountComponent()又會去調(diào)用C++層Instance::mountComponent()方法棋弥。

React組件可以分為兩種:

  • 元組件:框架內(nèi)置的,可以直接使用的組件诚欠。例如:View顽染、Image等。它在React Native中用ReactNativeBaseComponent來描述轰绵。
  • 復(fù)合組件:用戶封裝的組件粉寞,一般可以通過React.createClass()來構(gòu)建,提供render()方法來返回渲染目標藏澳。它在React Native中用ReactCompositeComponent來描述仁锯。

instantiateReactComponent(node, shouldHaveDebugID)方法就是根據(jù)對象的type生成元組件或者復(fù)合組件。

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;

  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    var type = element.type;

    if (typeof type !== 'function' && typeof type !== 'string') {
      var info = '';
      if (process.env.NODE_ENV !== 'production') {
        if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
          info += ' You likely forgot to export your component from the file ' + 'it\'s defined in.';
        }
      }
      info += getDeclarationErrorAddendum(element._owner);
      !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s', type == null ? type : typeof type, info) : _prodInvariant('130', type == null ? type : typeof type, info) : void 0;
    }

    //如果對象的type為string翔悠,則調(diào)用ReactHostComponent.createInternalComponent(element)來注入生成組件的邏輯
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    }
    //如果是內(nèi)部元組件,則創(chuàng)建一個type實例
    else if (isInternalComponentType(element.type)) {
      // This is temporarily available for custom components that are not string
      // representations. I.e. ART. Once those are updated to use the string
      // representation, we can drop this code path.
      instance = new element.type(element);

      // We renamed this. Allow the old name for compat. :(
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } 
    //否則野芒,則是用戶創(chuàng)建的復(fù)合組件蓄愁,這個時候創(chuàng)建一個ReactCompositeComponentWrapper實例,該實例用來描述復(fù)合組件
    else {
      instance = new ReactCompositeComponentWrapper(element);
    }
    //當(dāng)對象為string或者number時狞悲,調(diào)用ReactHostComponent.createInstanceForText(node)來注入組件生成邏輯撮抓。
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
    !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Encountered invalid React node of type %s', typeof node) : _prodInvariant('131', typeof node) : void 0;
  }

  if (process.env.NODE_ENV !== 'production') {
    process.env.NODE_ENV !== 'production' ? warning(typeof instance.mountComponent === 'function' && typeof instance.receiveComponent === 'function' && typeof instance.getHostNode === 'function' && typeof instance.unmountComponent === 'function', 'Only React Components can be mounted.') : void 0;
  }

  // These two fields are used by the DOM and ART diffing algorithms
  // respectively. Instead of using expandos on components, we should be
  // storing the state needed by the diffing algorithms elsewhere.
  instance._mountIndex = 0;
  instance._mountImage = null;

  if (process.env.NODE_ENV !== 'production') {
    instance._debugID = shouldHaveDebugID ? getNextDebugID() : 0;
  }

  // Internal instances should fully constructed at this point, so they should
  // not get any new fields added to them at this point.
  if (process.env.NODE_ENV !== 'production') {
    if (Object.preventExtensions) {
      Object.preventExtensions(instance);
    }
  }

  return instance;
}

該方法根據(jù)對象的type生成元組件或者復(fù)合組件,具體流程如下:

  1. 如果對象的type為string摇锋,則調(diào)用ReactHostComponent.createInternalComponent(element)來注入生成組件的邏輯丹拯,如果是內(nèi)部元組件,則創(chuàng)建一個type實例荸恕,
  2. 否則乖酬,則是用戶創(chuàng)建的復(fù)合組件,這個時候創(chuàng)建一個ReactCompositeComponentWrapper實例融求,該實例用來描述復(fù)合組件咬像。
  3. 當(dāng)對象為string或者number時,調(diào)用ReactHostComponent.createInstanceForText(node)來注入組件生成邏輯。
  4. 以上都不是县昂,則報錯肮柜。

我們可以看到,UI渲染主要通過UIManager來完成倒彰,UIManager是一個ReactModule审洞,UIManager.js里的操作都會對應(yīng)到UIManagerModule里來。我們接著來看看Java層的渲染流程

Java層的渲染流程

Java 層渲染

Java層的組件渲染分為以下幾步:

  1. JS層通過C++層把創(chuàng)建View的請求發(fā)送給Java層的UIManagerModule待讳。
  2. UIManagerModule通過UIImplentation對操作請求進行包裝芒澜。
  3. 包裝后的操作請求被發(fā)送到View處理隊列UIViewOperationQueue隊列中等待處理。
  4. 實際處理View時耙箍,根據(jù)class name查詢對應(yīng)的ViewNManager撰糠,然后調(diào)用原生View的方法對View進行相應(yīng)的操作。

通信機制

Java層與JavaScript層的相互調(diào)用都不是直接完成的辩昆,而是間接通過C++層來完成的阅酪。在介紹通信機制之前我們先來理解一些基本的概念。

JavaScript Module注冊表

說起JavaScript Module注冊表汁针,我們需要先理解幾個類/接口:

  • JavaScriptModule:這是一個接口术辐,JS Module都會繼承此接口,它表示在JS層會有一個相同名字的js文件施无,該js文件實現(xiàn)了該接口定義的方法辉词,JavaScriptModuleRegistry會利用動態(tài)代理將這個接口生成代理類,并通過C++傳遞給JS層猾骡,進而調(diào)用JS層的方法瑞躺。
  • JavaScriptModuleRegistration 用來描述JavaScriptModule的相關(guān)信息,它利用反射獲取接口里定義的Method兴想。
  • JavaScriptModuleRegistry:JS Module注冊表幢哨,內(nèi)部維護了一個HashMap:HashMap<Class<? extends JavaScriptModule>, JavaScriptModuleRegistration> mModuleRegistrations,JavaScriptModuleRegistry利用動態(tài)代理生成接口JavaScriptModule對應(yīng)的代理類嫂便,再通過C++傳遞到JS層捞镰,從而調(diào)用JS層的方法。

Java Module注冊表

要理解Java Module注冊表毙替,我們同樣也需要理解3個類/接口:

  • NativeModule:是一個接口岸售,實現(xiàn)了該接口則可以被JS層調(diào)用,我們在為JS層提供Java API時通常會繼承BaseJavaModule/ReactContextBaseJavaModule厂画,這兩個類就實現(xiàn)了NativeModule接口凸丸。
  • ModuleHolder:NativeModule的一個Holder類,可以實現(xiàn)NativeModule的懶加載木羹。
  • NativeModuleRegistry:Java Module注冊表甲雅,內(nèi)部持有Map:Map<Class<? extends NativeModule>, ModuleHolder> mModules解孙,NativeModuleRegistry可以遍歷并返回Java Module供調(diào)用者使用。

其中 NativeModuleRegistry是在createReactContext()方法里構(gòu)建的抛人。
JavaScriptModuleRegistry是在CatalystInstanceImpl的構(gòu)建方法里構(gòu)建的.
這些都是在CatalystInstanceImpl的構(gòu)建方法里通過native方法initializeBridge()傳入了C++層弛姜,如下所示:

void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    // This executor is actually a factory holder.
    JavaScriptExecutorHolder* jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {


  instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
                              jseh->getExecutorFactory(),
                              folly::make_unique<JMessageQueueThread>(jsQueue),
                              folly::make_unique<JMessageQueueThread>(moduleQueue),
                              buildModuleRegistry(std::weak_ptr<Instance>(instance_),
                                                  javaModules, cxxModules));
}
  • callback:CatalystInstanceImpl的靜態(tài)內(nèi)部類ReactCallback,負責(zé)接口回調(diào)妖枚。
  • jsExecutor:JS執(zhí)行器廷臼,將JS的調(diào)用傳遞給C++層。
  • jsQueue.getJSQueueThread():JS線程绝页,通過mReactQueueConfiguration.getJSQueueThread()獲得荠商,mReactQueueConfiguration通過ReactQueueConfigurationSpec.createDefault()創(chuàng)建。
  • moduleQueue:Native線程续誉,通過mReactQueueConfiguration.getNativeModulesQueueThread()獲得莱没,mReactQueueConfiguration 通過 ReactQueueConfigurationSpec.createDefault()創(chuàng)建。
  • javaModules:java modules酷鸦,來源于mJavaRegistry.getJavaModules(this)饰躲。它對應(yīng)了C++層JavaModuleWrapper.cpp, JS在Java的時候最終會調(diào)用到這個類的inovke()方法上臼隔。
  • cxxModules:c++ modules嘹裂,來源于mJavaRegistry.getCxxModules()。ModuleHolder是NativeModule的一個Holder類摔握,可以實現(xiàn)NativeModule的懶加載寄狼。

cxxModules javaModules這兩個集合在CatalystInstanceImpl::initializeBridge()被打包成ModuleRegistry傳入Instance.cpp 中,并在NativeToJsBridge的構(gòu)造方法中傳給JsToNativeBridge氨淌,以后JS如果調(diào)用Java就可以通過 ModuleRegistry 來進行調(diào)用泊愧。

JNI作為C++與Java的橋梁,JSC作為C++與JavaScript的橋梁盛正,而C++最終連接了Java與JavaScript拼卵。


RN通信結(jié)構(gòu)
Java 調(diào)用 JS
JS 調(diào)用 Java
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛮艰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雀彼,老刑警劉巖壤蚜,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異徊哑,居然都是意外死亡袜刷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門莺丑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來著蟹,“玉大人墩蔓,你說我怎么就攤上這事∠舳梗” “怎么了奸披?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涮雷。 經(jīng)常有香客問我阵面,道長,這世上最難降的妖魔是什么洪鸭? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任样刷,我火速辦了婚禮,結(jié)果婚禮上览爵,老公的妹妹穿的比我還像新娘置鼻。我一直安慰自己,他們只是感情好蜓竹,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布箕母。 她就那樣靜靜地躺著,像睡著了一般梅肤。 火紅的嫁衣襯著肌膚如雪司蔬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天姨蝴,我揣著相機與錄音俊啼,去河邊找鬼。 笑死左医,一個胖子當(dāng)著我的面吹牛授帕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浮梢,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼跛十,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秕硝?” 一聲冷哼從身側(cè)響起芥映,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎远豺,沒想到半個月后奈偏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡躯护,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年惊来,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棺滞。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡裁蚁,死狀恐怖矢渊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枉证,我是刑警寧澤矮男,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站刽严,受9級特大地震影響昂灵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舞萄,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一眨补、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧倒脓,春花似錦撑螺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饲做,卻和暖如春线婚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盆均。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工塞弊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泪姨。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓游沿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肮砾。 傳聞我的和親對象是個殘疾皇子诀黍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354