ReactNative For Android 框架啓動核心路徑剖析

版權聲明:本文由王少鳴原創文章,轉載請註明出處: 
文章原文連接:https://www.qcloud.com/community/article/144前端

來源:騰雲閣 https://www.qcloud.com/communityjava

 

前面給你們分析過 ReactNative For Android (RN4A) 的通訊機制,此次咱們從源碼出發,分析下RN4A的啓動過程。啓動過程基於通訊機制,涉及通訊機制原理你們能夠查看前一篇文章,本篇不贅述。react


上面是2016 React.js Conf FB 工程師分享的RN啓動時序圖,整個過程比較清晰,先啓動終端運行時,隨後由終端上下文去啓動JS的運行時,進而佈局,最後再由終端進行渲染,最後將View添加到RootView上。那接下來,咱們先理解幾個概念,方便後續咱們對整個啓動過程的理解。緩存

模塊:

模塊即暴露給調用方的API集合,在RN4A存在兩種模塊。app

一種是Native層暴露給Js層的API集合模塊,即NativeModule,如ToastModule,DialogModule,或是建立View的UIManagerModule。業務方能夠經過實現NativeModule自定義模塊,經過重寫getName將模塊名暴露給Js層,經過註解的方式將API暴露給Js層調用。框架

另外一種是Js層暴露給Java層的API集合模塊,即JavascriptModule,如DeviceEventEmitter,AppRegistry等。業務方能夠經過繼承JavaScriptModule接口自定義接口模塊,申明與Js層相應的方法便可。ide

不管是NativeModule仍是JavascriptModule,在Js層存在與之相互映射同名的Module,Js層經過require引用Module。佈局

模塊註冊表:

各模塊信息統一收集到模塊註冊表。一樣,在RN4A中存在兩種模塊註冊表,一是由集合全部Java層模塊接口信息的NativeModuleRegistry,另外一種是集合全部Js層模塊接口信息的JavascriptModuleRegistry。在啓動RN4A後,終端將註冊表信息存入與前端互通的全局變量__fbBatchedBridgeConfig 中,使得Js層與Java層存在一樣的模塊註冊表。ui

正如上面FB攻城獅提出的時序圖,從終端啓動,入口是ReactRootView.startReactApplication,在構造JavaScriptExecutor&JSBundleLoader後,進而經過ReactContextInitAsycnTask去建立ReactContext,這部分主要建立了NativeModules,JavaScriptModule及其對的註冊表,負責Js與Java通訊的高層接口CatalystInstance等。在建立完ReactContext後,經過CatalystInstance獲取AppRegistry並調用其runApplication啓動Js Application。總體流程以下:this

接下來進入正題,從源碼來分析RN4A的啓動(爲閱讀方便,源碼適當裁剪)

ReactInstanceManager createReactContextInBackground,經過AysncTask初始化ReactNative上下文。mJSModuleName是與前端約定好所要啓動的JS Application Name。mLauncahOptions是終端啓動前端Application可選的傳入的參數。

/**
 * ReactRootView.java
 */
public void startReactApplication(
    ReactInstanceManager reactInstanceManager,
    String moduleName,
    @Nullable Bundle launchOptions) {
  UiThreadUtil.assertOnUiThread();

  mReactInstanceManager = reactInstanceManager;
  mJSModuleName = moduleName;
  mLaunchOptions = launchOptions;

  if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
    mReactInstanceManager.createReactContextInBackground();
  }

  if (mWasMeasured && mIsAttachedToWindow) {
    mReactInstanceManager.attachMeasuredRootView(this);
    mIsAttachedToInstance = true;
    getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
  } else {
    mAttachScheduled = true;
  }
}
`

createReactContextInBackground最終調用到recreateReactContextInBackgroundFromBundleFile。這裏會建立兩個Key Obj : JSCJavaScriptExecutor&JSBundleLoader。
JSCJavaScriptExecutor繼承自JavaScriptExecutor,在JSCJavaScriptExecutor.class加載會加載ReactNative的SO,而且,在初始JSCJavaScriptExecutor時會調用initialze去初始C++層ReactNative與JSC的通訊框架等。
JSBundleLoader緩存了JsBundle的信息,封裝了上層加載JsBundle相關接口,CatalystInstance經過其間接調用ReactBridge去加載文件。

/**
 * ReactInstanceManagerImpl.java
 */
private void recreateReactContextInBackgroundFromBundleFile() {
  recreateReactContextInBackground(
      new JSCJavaScriptExecutor.Factory(),
      JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
}

建立完JSCJavaScriptExecutor&JSBundleLoader後,execute ReactContextInitAsyncTask繼續初始化ReactContext。

/**
 * ReactInstanceManagerImpl.java
 */
private void recreateReactContextInBackground(
    JavaScriptExecutor.Factory jsExecutorFactory,
    JSBundleLoader jsBundleLoader) {

  ReactContextInitParams initParams =
      new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
  if (!mIsContextInitAsyncTaskRunning) {

    ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();
    initTask.execute(initParams);
    mIsContextInitAsyncTaskRunning = true;
  } else {
    mPendingReactContextInitParams = initParams;
  }
}

ReactContextInitAsyncTask爲建立ReactContext的核心類,在執行初始化前會銷燬先前的上下文,保證只存在一個上下文。隨後,調用createReactContext進一步建立ReactContext。在建立完React Context後會調用setUpReactContext,進而通知DevSupportManager更新上下文,更新生命週期,將ReactRootView作爲Root View傳遞給UIManagerModule,調用AppRegistry的runApplication去啓動Js Application等。

/**
 * ReactInstanceManagerImpl$ReactContextInitAsynTask.java
 */
private final class ReactContextInitAsyncTask extends
    AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
  @Override
  protected void onPreExecute() {
    if (mCurrentReactContext != null) {
      tearDownReactContext(mCurrentReactContext);
      mCurrentReactContext = null;
    }
  }

  @Override
  protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
    Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
    try {
      JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
      return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
    } catch (Exception e) {
      // Pass exception to onPostExecute() so it can be handled on the main thread
      return Result.of(e);
    }
  }

  @Override
  protected void onPostExecute(Result<ReactApplicationContext> result) {
    try {
      setupReactContext(result.get());
    } catch (Exception e) {
      mDevSupportManager.handleException(e);
    } finally {
      mIsContextInitAsyncTaskRunning = false;
    }

    // Handle enqueued request to re-initialize react context.
    if (mPendingReactContextInitParams != null) {
      recreateReactContextInBackground(
          mPendingReactContextInitParams.getJsExecutorFactory(),
          mPendingReactContextInitParams.getJsBundleLoader());
      mPendingReactContextInitParams = null;
    }
  }
}

在CreateReactContext中,主要有如下5個key path:

  1. 經過Builder構建上文概念講過的NativeModuleRegistry及JavaScriptModuleConfig;
  2. 建立ReactApplicationContext。ReactApplicationContext繼承自ContextWrapper,主要緩存了Application Context,Activity Context,ReactNative處理消息的三個thread(下篇講述),還有就是全局控制JS調用致使Native Module Crash的NativeModuleCallExceptionHandler,在初始化ReactInstanceManager的時候傳入,而且要關閉DeveloperSupport後才能夠啓用,假如不傳,則默認交由DevSupportManger去處理;
  3. 建立ReactPackage。ReactPackage主要經過createNativeModules、createJSModules和createViewManagers等API去建立本地模塊,JS模塊及視圖組件等。ReactPackage分爲framework的CoreModulesPackage和業務方可選的基礎MainReactPackage,CoreModulesPackage封裝了大部分通訊,調試核心類,如UIManagerModule,這個負責控制Js層Dom到Native View的核心類;
  4. 建立CatalystInstance。CatalystInstance並不直接面向開發者,開發者通ReactInstanceManger間接操做CatalystInstance。CatalystInstance持有對ReactBridge的引用,主要經過ReactBridge這個JNI類去實現Java層與Js層的通訊,ReactBridge由CatalystInstance的Constructor建立。同時初始化的時候調用了ReactQueueConfigurationSpec.createDefault建立了ReactNative通訊的兩個線程 JsQueueThread&NativeModulesQueueThread;
  5. 調用reactContext.initializeWithInstance進一步將建立完的CatalystInstance及線程等緩存在ReactContext中;
  6. 調用catalystInstance.runJSBundle加載解析Jsbundle;
/**
 * ReactInstanceManagerImpl.java
 *
 * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
 */
private ReactApplicationContext createReactContext(
    JavaScriptExecutor jsExecutor,
    JSBundleLoader jsBundleLoader) {

  mSourceUrl = jsBundleLoader.getSourceUrl();
  NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
  JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();

  ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
  if (mUseDeveloperSupport) {
    reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
  }

  CoreModulesPackage coreModulesPackage =
        new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
  processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);

  for (ReactPackage reactPackage : mPackages) {
      processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
  }

  nativeModuleRegistry = nativeRegistryBuilder.build();
  javaScriptModulesConfig = jsModulesBuilder.build();

  NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
      ? mNativeModuleCallExceptionHandler
      : mDevSupportManager;
  CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
      .setJSExecutor(jsExecutor)
      .setRegistry(nativeModuleRegistry)
      .setJSModulesConfig(javaScriptModulesConfig)
      .setJSBundleLoader(jsBundleLoader)
      .setNativeModuleCallExceptionHandler(exceptionHandler);

  CatalystInstance catalystInstance= catalystInstanceBuilder.build();
  if (mBridgeIdleDebugListener != null) {
    catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
  }
  reactContext.initializeWithInstance(catalystInstance);
  catalystInstance.runJSBundle();

  return reactContext;
}

ReactBridge由CatalystInstance的Constructor建立。

/**
 *  CatalystInstanceImpl.java
 */
private CatalystInstanceImpl(
    final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
    final JavaScriptExecutor jsExecutor,
    final NativeModuleRegistry registry,
    final JavaScriptModulesConfig jsModulesConfig,
    final JSBundleLoader jsBundleLoader,
    NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
  mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
      ReactQueueConfigurationSpec,
      new NativeExceptionHandler());
  mBridgeIdleListeners = new CopyOnWriteArrayList<>();
  mJavaRegistry = registry;
  mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
  mJSBundleLoader = jsBundleLoader;
  mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
  mTraceListener = new JSProfilerTraceListener();

  try {
    mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
        new Callable<ReactBridge>() {
          @Override
          public ReactBridge call() throws Exception {
            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge");
            try {
              return initializeBridge(jsExecutor, jsModulesConfig);
            } finally {
              Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            }
          }
        }).get();
  } catch (Exception t) {
    throw new RuntimeException("Failed to initialize bridge", t);
  }
}

ReactBridge將註冊表信息存入與前端互通的全局變量 __fbBatchedBridgeConfig 中,使得Js層與Java層存在一樣的模塊註冊表。

/**
 *  CatalystInstanceImpl.java
 */
private ReactBridge initializeBridge(
    JavaScriptExecutor jsExecutor,
    JavaScriptModulesConfig jsModulesConfig) {

  ReactBridge bridge = new ReactBridge(jsExecutor, new NativeModulesReactCallback(),
      mReactQueueConfiguration.getNativeModulesQueueThread());

  Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig");
  bridge.setGlobalVariable(
      "__fbBatchedBridgeConfig",
      buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
  bridge.setGlobalVariable(
      "__RCTProfileIsProfiling",

  return bridge;
}

調用catalystInstance.runJSBundle加載解析Jsbundle。假如在解析過程當中出現Exception,統一交給NativeModuleCallExceptionHandler處理,建議開發者設置本身的NativeModuleCallExceptionHandler,能夠歸避部分Crash(SyntaxError: Unexpected token ‘<‘ 或 SyntaxError: Unexpected end of script)。

/**
 *  CatalystInstanceImpl.java
 */
public void runJSBundle() {
  try {
    mJSBundleHasLoaded = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
        new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            incrementPendingJSCalls();
            try {
              mJSBundleLoader.loadScript(mBridge);
              Systrace.registerListener(mTraceListener);
            } catch (JSExecutionException e) {
              mNativeModuleCallExceptionHandler.handleException(e);
            } finally {
              Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            }
            return true;
          }
        }).get();
  } catch (Exception t) {
    throw new RuntimeException(t);
  }
}

在建立完React Context後會執行ReactContextInitAsyncTask的onPostExecute,從而調用setUpReactContext,會將ReactRootView作爲Root View傳遞給UIManagerModule,此後Js經過UIManager建立的View都會add到該View上。

/**
 * ReactInstanceManagerImpl.java
 */
@Override
public void attachMeasuredRootView(ReactRootView rootView) {
  UiThreadUtil.assertOnUiThread();
  if(mIsNeedDetachView){
    Log.d(ReactConstants.QZONE_REACT_SRC_TAG,"attachMeasuredRootView do add");
    mAttachedRootViews.add(rootView);
    // If react context is being created in the background, JS application will be started
    // automatically when creation completes, as root view is part of the attached root view list.
    if (!mIsContextInitAsyncTaskRunning && mCurrentReactContext != null) {
      attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
    }
  }else{
    Log.d(ReactConstants.QZONE_REACT_SRC_TAG,"attachMeasuredRootView do nothing");
  }

}

在綁定完RootView後,經過CatalystInstance獲取AppRegistry這個JSModule後,進一步調用runApplication啓動Js Application。

/**
 * ReactInstanceManagerImpl.java
 */
private void attachMeasuredRootViewToInstance(
    ReactRootView rootView,
    CatalystInstance catalystInstance) {

  rootView.removeAllViews();
  rootView.setId(View.NO_ID);

  UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
  int rootTag = uiManagerModule.addMeasuredRootView(rootView);
  @Nullable Bundle launchOptions = rootView.getLaunchOptions();
  WritableMap initialProps = launchOptions != null
      ? Arguments.fromBundle(launchOptions)
      : Arguments.createMap();
  String jsAppModuleName = rootView.getJSModuleName();

  WritableNativeMap appParams = new WritableNativeMap();
  appParams.putDouble("rootTag", rootTag);
  appParams.putMap("initialProps", initialProps);
  catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}

ReactNative中Java與Js通訊再也不贅述。至此,啓動Js層AppRegistry的runApplication啓動Js Application。

/**
* AppRegistry.js
*/
runApplication: function(appKey: string, appParameters: any): void {
  console.log(
    'Running application "' + appKey + '" with appParams: ' +
    JSON.stringify(appParameters) + '. ' +
    '__DEV__ === ' + String(__DEV__) +
    ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') +
    ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON')
  );
  invariant(
    runnables[appKey] && runnables[appKey].run,
    'Application ' + appKey + ' has not been registered. This ' +
    'is either due to a require() error during initialization ' +
    'or failure to call AppRegistry.registerComponent.'
  );
  runnables[appKey].run(appParameters);
},
相關文章
相關標籤/搜索