React Native JSBundle拆包之原理篇

概述

RN做爲一款很是優秀的移動端跨平臺開發框架,在近幾年獲得衆多開發者的承認。縱觀如今接入RN的大廠,如qq音樂、菜鳥、去哪兒,無疑不是將RN做爲重點技術棧進行研發。html

不過,熟悉RN的開發者也知道,早期的RN版本中打出來的包都只有一個jsbundle,而這個jsbundle裏面包含了全部代碼(RN源碼、第三方庫代碼和本身的業務代碼)。若是是純RN代碼倒沒什麼關係,但大部分的大廠都是在原生應用內接入RN的,並且一個RN中又包含許多不一樣的業務,這些不一樣的業務極可能是不一樣部門開發的,這樣一個庫中就有許許多多的重複的RN代碼和第三方庫代碼。java

因此,通常作法都是將重複的RN代碼和第三方庫打包成一個基礎包,而後各個業務在基礎包的基礎上進行開發,這樣作的好處是能夠下降對內存的佔用,減小加載時間,減小熱更新時流量帶寬等,在優化方面起到了很是大的做用。node

拆包流派

moles-packer

moles-packer 是由攜程框架團隊研發的,與攜程moles框架配套使用的React Native 打包和拆包工具,同時支持原生的 React Native 項目。react

特色:重寫了react native自帶的打包工具,適合RN0.4.0版本以前的分包。維護少,如今基本沒有多少人使用,兼容性差。android

diff patch

diff patch大體的作法就是先打個正常的完整的jsbundle,而後再打個只包含了基礎引用的基礎包,比對一下patch,得出業務包,這樣基礎包和業務包都有了,更新時更新業務包便可。差分包的工具能夠google-diff-match-patchc++

metro bundle

目前,最好的RN分包方案仍是facebook官方提供的metro bundle,此方案是fb在0.50版本引入的,並隨着RN版本的迭代不斷完善。也便是說,只要你使用的是0.50以上的RN版本,就可使用metro bundle進行差分包進行熱更新。git

配置內容比較多,這裏主要看createModuleIdFactory和processModuleFilte兩個配置參數。github

原理篇

RN啓動分析

爲了更好的理解RN的分包和和加載機制,下面經過源碼來看看RN的啓動過程。shell

"dependencies": {
    "react": "16.6.1",
    "react-native": "0.57.7",
    "react-navigation": "^2.0.1"
  },

注:本篇使用基於最新的0.57.7版本進行分析npm

1,JS端啓動流程

index.js 做爲RN應用的默認入口,源碼以下:

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

AppRegistry是全部 RN應用的 JS 入口,應用的根組件經過AppRegistry.registerComponent方法註冊本身,而後原生系統才能夠加載應用的代碼包而且在啓動完成以後經過調用AppRegistry.runApplication來真正運行應用。registerComponent對應的源碼以下:

/**
   * Registers an app's root component.
   *
   * See http://facebook.github.io/react-native/docs/appregistry.html#registercomponent
   */
  registerComponent(
    appKey: string,
    componentProvider: ComponentProvider,
    section?: boolean,
  ): string {
    runnables[appKey] = {
      componentProvider,
      run: appParameters => {
        renderApplication(
          componentProviderInstrumentationHook(componentProvider),
          appParameters.initialProps,
          appParameters.rootTag,
          wrapperComponentProvider && wrapperComponentProvider(appParameters),
          appParameters.fabric,
        );
      },
    };
    if (section) {
      sections[appKey] = runnables[appKey];
    }
    return appKey;
  },

RN項目index.js文件的在調用registerComponent 方法時默認傳入了 appKey、ComponentProvider 兩個參數,而section是能夠不用傳的。而後,registerComponent方法會調用renderApplication方法參數並調用了 renderApplication 方法。renderApplication的源碼以下:

function renderApplication<Props: Object>(
  RootComponent: React.ComponentType<Props>,
  initialProps: Props,
  rootTag: any,
  WrapperComponent?: ?React.ComponentType<*>,
  fabric?: boolean,
  showFabricIndicator?: boolean,
) {
 
  invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
 
  let renderable = (
    <AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
      <RootComponent {...initialProps} rootTag={rootTag} />
      {fabric === true && showFabricIndicator === true ? (
        <ReactFabricIndicator />
      ) : null}
    </AppContainer>
  );

renderApplication中調用了AppContainer組件來封裝當前rootVIew組件,並最終調用AppRegistry.runApplication來啓動應用程序。

/**
   * Loads the JavaScript bundle and runs the app.
   *
   * See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
   */
  runApplication(appKey: string, appParameters: any): void {
    const msg =
      'Running application "' +
      appKey +
      '" with appParams: ' +
      JSON.stringify(appParameters) +
      '. ' +
      '__DEV__ === ' +
      String(__DEV__) +
      ', development-level warning are ' +
      (__DEV__ ? 'ON' : 'OFF') +
      ', performance optimizations are ' +
      (__DEV__ ? 'OFF' : 'ON');
    infoLog(msg);
    BugReporting.addSource(
      'AppRegistry.runApplication' + runCount++,
      () => msg,
    );
    invariant(
      runnables[appKey] && runnables[appKey].run,
      'Application ' +
        appKey +
        ' has not been registered.\n\n' +
        "Hint: This error often happens when you're running the packager " +
        '(local dev server) from a wrong folder. For example you have ' +
        'multiple apps and the packager is still running for the app you ' +
        'were working on before.\nIf this is the case, simply kill the old ' +
        'packager instance (e.g. close the packager terminal window) ' +
        'and start the packager in the correct app folder (e.g. cd into app ' +
        "folder and run 'npm start').\n\n" +
        'This error can also happen due to a require() error during ' +
        'initialization or failure to call AppRegistry.registerComponent.\n\n',
    );

    SceneTracker.setActiveScene({name: appKey});
    runnables[appKey].run(appParameters);
  },

在 runApplication 方法中,RN會經過 runnables[appKey] && runnables[appKey].run 來檢查是否能夠找到appKey對應的module組件,若是沒有,則會拋出異常。

那麼,RN編寫的頁面又是如何在Android系統中顯示的呢?那就得看看RN的Android端源碼了。

2,Android啓動流程

打開RN的Android項目,能夠發現,Android的src目錄下就只有MainActivity和 MainApplication 兩個Java類。其中,MainActivity 爲原生層應用程序的入口文件,MainApplication爲Android應用程序入口文件。
MainActivity.java文件的源碼以下:

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    @Override
    protected String getMainComponentName() {
        return "RNDemo";
    }
}

MainActivity類的代碼很簡單,該類繼承自 ReactActivity 並實現 getMainComponentName 方法,getMainComponentName方法返回與 AppRegistry.registerComponent 的 appKey 相同的名稱。

MainApplication類也比較簡單,源碼以下:

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage()
      );
    }

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}

MainApplication 主要完成了三件事:

  • 實現 ReactApplication 接口,重寫 getReactNativeHost 方法,返回ReactNativeHost實例。
  • 定義並初始化 ReactNativeHost,實現getUseDeveloperSupport、getPackages、getJSMainModuleName 方法,完成初始化設置。
  • 在 onCreate 方法中,調用SoLoader的init方法,啓動C++層邏輯代碼的初始化加載。

ReactActivity

MainActivity 繼承 ReactActivity 類,並重寫了getMainComponentName 方法,而且方法的返回值須要和咱們在JS端的值保持一致。ReactActivity最核心的就是ReactActivityDelegate。

protected ReactActivity() {
    mDelegate = createReactActivityDelegate();
  }

/**
   * 在構造時調用,若是您有自定義委託實現,則覆蓋.
   */
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName());
  }

很明顯,ReactActivity 類採用了委託的方式,將全部行爲全權交給了 ReactActivityDelegate 去處理。這樣作的好處是,下降代碼耦合,提高了可擴展性。下面咱們看一下ReactActivityDelegate類:

public class ReactActivityDelegate {
 
  private final @Nullable Activity mActivity;
  private final @Nullable FragmentActivity mFragmentActivity;
  private final @Nullable String mMainComponentName;
 
  private @Nullable ReactRootView mReactRootView;
  private @Nullable DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
  
  ...
 
  public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
    mActivity = activity;
    mMainComponentName = mainComponentName;
    mFragmentActivity = null;
  }
 
  public ReactActivityDelegate(
    FragmentActivity fragmentActivity,
    @Nullable String mainComponentName) {
    mFragmentActivity = fragmentActivity;
    mMainComponentName = mainComponentName;
    mActivity = null;
  }
   
  protected @Nullable Bundle getLaunchOptions() {
    return null;
  }
 
  protected ReactRootView createRootView() {
    return new ReactRootView(getContext());
  }
 
  /**
   * Get the {@link ReactNativeHost} used by this app. By default, assumes
   * {@link Activity#getApplication()} is an instance of {@link ReactApplication} and calls
   * {@link ReactApplication#getReactNativeHost()}. Override this method if your application class
   * does not implement {@code ReactApplication} or you simply have a different mechanism for
   * storing a {@code ReactNativeHost}, e.g. as a static field somewhere.
   */
  protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
  }
 
  public ReactInstanceManager getReactInstanceManager() {
    return getReactNativeHost().getReactInstanceManager();
  }
 
  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);
  }
 
  ... 中間省略生命週期、返回事件、權限請求的方法
 
  private Context getContext() {
    if (mActivity != null) {
      return mActivity;
    }
    return Assertions.assertNotNull(mFragmentActivity);
  }
 
  private Activity getPlainActivity() {
    return ((Activity) getContext());
  }
}

ReactActivityDelegate類重點關注loadApp方法, loadApp 方法主要作了三件事:

  • 建立 RootView實例;
  • 調用 RootView 實例的 startReactApplication 方法,將ReactInstanceManager實例、appKey、啓動時初始化參數做爲參數傳遞過去;
  • 將 ReactRootView 設置爲 MainActivity 佈局視圖;

ReactRootView

在loadApp裏面,首先建立一個ReactRootView,這個mReactRootView繼承自FrameLayout,做爲界面的跟佈局,而後調用mReactRootView.startReactApplication()方法啓動RN應用。涉及的源碼以下:

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

  
    Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");

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

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

   //執行界面測量
    if (mWasMeasured) {
      attachToReactInstanceManager();
    }
  }

這裏會執行到mReactInstanceManager.createReactContextInBackground()這個方法去生成reactnative的上下文對象。

public void createReactContextInBackground() {
    Assertions.assertCondition(
        !mHasStartedCreatingInitialContext,
        "createReactContextInBackground should only be called when creating the react " +
            "application for the first time. When reloading JS, e.g. from a new file, explicitly" +
            "use recreateReactContextInBackground");

    mHasStartedCreatingInitialContext = true;
    recreateReactContextInBackgroundInner();
  }

上面代碼首先將mHasStartedCreatingInitialContext置爲true,而後在 startReactApplication 方法中調用了 ReactInstanceManager 實例的 createReactContextInBackground 方法。

ReactInstanceManager

@ThreadConfined(UI)
  private void recreateReactContextInBackgroundInner() {
 
    if (mUseDeveloperSupport
        && mJSMainModulePath != null
        && !Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
      final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
 
      // 若是啓用了遠程JS調試,從dev服務器加載。
      if (mDevSupportManager.hasUpToDateJSBundleInCache() &&
          !devSettings.isRemoteJSDebugEnabled()) {
 
        // 若是從服務器下載了最新的捆綁包,禁用遠程JS調試,始終使用它。
        onJSBundleLoadedFromServer(null);
 
      } else if (mBundleLoader == null) {
 
        mDevSupportManager.handleReloadJS();
 
      } else {
 
        mDevSupportManager.isPackagerRunning(
            new PackagerStatusCallback() {
              @Override
              public void onPackagerStatusFetched(final boolean packagerIsRunning) {
              
                UiThreadUtil.runOnUiThread(
                    new Runnable() {
                      @Override
                      public void run() {
                        if (packagerIsRunning) {
 
                          mDevSupportManager.handleReloadJS();
                        } else {
 
                          //若是dev服務器關閉,請禁用遠程JS調試。
                          devSettings.setRemoteJSDebugEnabled(false);
                          recreateReactContextInBackgroundFromBundleLoader();
                        }
                      }
                    });
              }
            });
      }
      return;
    }
    
    // 從 本地路徑 加載 jsBundle
    recreateReactContextInBackgroundFromBundleLoader();
  }
 
 
  @ThreadConfined(UI)
  private void recreateReactContextInBackgroundFromBundleLoader() {
    // 從BundleLoader加載
    recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
  }

在 recreateReactContextInBackgroundInner 方法中,首先判斷當前環境是否爲開發者模式,在開發者模式下會執行 onJSBundleLoadedFromServer 方法從服務器加載 jsBundle文件。不然執行 recreateReactContextInBackgroundFromBundleLoader 方法從本地目錄加載。

在 recreateReactContextInBackgroundFromBundleLoader 方法中調用了 recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader) 方法。jsExecutorFactory 爲 C++ 和 JS 雙向通訊的中轉站。

jsBundleLoader 爲 bundle 加載器,根據 ReactNativeHost 中的配置決定從哪裏加載bundle文件。

private void recreateReactContextInBackground(
    JavaScriptExecutorFactory jsExecutorFactory,
    JSBundleLoader jsBundleLoader) {
  
  // 建立 ReactContextInitParams 對象
    final ReactContextInitParams initParams = new ReactContextInitParams(
      jsExecutorFactory,
      jsBundleLoader);
    if (mCreateReactContextThread == null) {
    
  // 開啓一個新的線程建立 ReactContext
      runCreateReactContextOnNewThread(initParams);
    } else {
      mPendingReactContextInitParams = initParams;
    }
  }

接下來,咱們看一下 runCreateReactContextOnNewThread 方法。

private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
   
   ...
 
    mCreateReactContextThread =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
               
                ....
 
                //因爲 destroy() 可能已經運行並將其設置爲false,所以在建立以前確保它爲true
                mHasStartedCreatingInitialContext = true;
 
                try {
 
                  // 標準顯示系統優先級,主要是改善UI的刷新
                  Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
 
                  // 建立 ReactApplicationContext 實例
                  final ReactApplicationContext reactApplicationContext =
                      createReactContext(
                          initParams.getJsExecutorFactory().create(),
                          initParams.getJsBundleLoader());
 
                  mCreateReactContextThread = null;
                
                  final Runnable maybeRecreateReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          if (mPendingReactContextInitParams != null) {
                            runCreateReactContextOnNewThread(mPendingReactContextInitParams);
                            mPendingReactContextInitParams = null;
                          }
                        }
                      };
                  Runnable setupReactContextRunnable =
                      new Runnable() {
                        @Override
                        public void run() {
                          try {
                            setupReactContext(reactApplicationContext);
                          } catch (Exception e) {
                            mDevSupportManager.handleException(e);
                          }
                        }
                      };
 
                  // 開啓線程執行
                  reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
                  UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
 
                } catch (Exception e) {
                  mDevSupportManager.handleException(e);
                }
              }
            });
    // 開啓線程執行
    mCreateReactContextThread.start();
  }

執行到這個的時候,系統最終會開啓異步任務ReactContextInitAsyncTask來建立上下文ReactApplicationContext,在ReactContextInitAsyncTask的doInBackground會調用createReactContext方法

ReactInstanceManager

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
 
    // 建立 ReactApplicationContext 實例
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
 
    ... 
 
 
    // 把各自的Module添加到對應的註冊表中,processPackages方法經過遍歷方式將在MainApplication 中 重寫的ReactNativeHost的getPackages方法中的packages加入到註冊表中
    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
    
    // 構建CatalystInstanceImpl實例
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
      // JS 執行通訊類
      .setJSExecutor(jsExecutor)
      // 註冊 Java 模塊
      .setRegistry(nativeModuleRegistry)
      // 設置JSBundle 加載方式
      .setJSBundleLoader(jsBundleLoader)
      // 設置異常處理器
      .setNativeModuleCallExceptionHandler(exceptionHandler);
 
    final CatalystInstance catalystInstance;
 
    // 建立 CatalystInstance 實例
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
 
 
    if (mJSIModulePackage != null) {
      catalystInstance.addJSIModules(mJSIModulePackage
        .getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder()));
    }
 
    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }
 
    // 調用 C++ 層代碼,把 Java Registry 轉換爲Json,再由 C++ 層傳送到 JS 層
    if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
      catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
    }
 
    // 開始加載JSBundle 
    catalystInstance.runJSBundle();
 
    // 關聯 ReactContext 與 CatalystInstance
    reactContext.initializeWithInstance(catalystInstance);
 
    return reactContext;
  }

在這個方法裏面,先生成nativeModuleRegistryBuilder和jsModulesBuilder,nativeModuleRegistryBuilder用來建立JavaModule註冊表,JavaModule註冊表將全部的JavaModule註冊到CatalystInstance中;jsModulesBuilder用來建立JavaScriptModule註冊表,JavaScriptModule註冊表將全部的JavaScriptModule註冊到CatalystInstance中。接着會執行下到 processPackage(coreModulesPackage,nativeModuleRegistryBuilder,jsModulesBuilder)代碼,CoreModulesPackage裏面封裝了RN Framework(包括native和js端)核心功能,包括:通訊、調試等,調用processPackage將coreModulesPackage裏面對應的NativeModules註冊到JavaModule註冊表中,對應的JSModules註冊到JavaScriptModule註冊表中,底下就會執行用戶自定義的ReactPackage,將對應的modules註冊到相應的註冊表中,JavaModule註冊表和JavaScriptModule註冊表註冊完畢以後,就是去生成一個catalystInstance,這個類主要是負責三端的通訊(經過ReactBridge,在catalystInstance的構造函數中調用initializeBridge方法生成),接着調用setGlobalVariable(Native方法)把Java Registry轉換爲Json,再由C++層傳送到JS層。catalystInstance相應的處理執行完以後,將其與reactContext關聯起來,最後經過catalystInstance加載bundle文件。

總的來講,createReactContext方法主要完成了如下操做:

  • 構建 ReactApplicationContext;
  • 註冊 Packages 原生模塊;
  • 構建 CatalystInstance 實例;
  • 經過 CatalystInstance 實例調用C++層代碼邏輯;
  • 調用 CatalystInstance 實例的 runJSBundle 方法加載 JSBundle。

到這裏,咱們基本開清楚了原生是如何加載JSBundle的:即經過 CatalystInstance 來加載 JSBundle 文件。

下面讓咱們繼續看 runJSBundle方法:

@Override
  public void runJSBundle() {
   
    Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
 
    // 經過 JSBundleLoader 去執行加載,不一樣的加載方式 JSBundleLoader 實現方式不一樣
    mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
 
    synchronized (mJSCallsPendingInitLock) {
 
      // 在 JS 線程上排隊加載 bundle,此時可能尚未運行。 在這裏設置它是安全的,由於它所關聯的任何工做都將在加載完成以後的JS線程上排隊執行。
      mAcceptCalls = true;
 
      for (PendingJSCall function : mJSCallsPendingInit) {
        function.call(this);
      }
      mJSCallsPendingInit.clear();
      mJSBundleHasLoaded = true;
    }
 
    Systrace.registerListener(mTraceListener);
  }

在 runJSBundle 方法中經過JSBundleLoader的 loadScript 方法去加載JSBundle,不一樣的加載方式 JSBundleLoader 實現方式不一樣。

JSBundleLoader

JSBundleLoader主要用於存儲 JS 包的信息,容許 CatalystInstance 經過 ReactBridge 加載正確的包。

public abstract class JSBundleLoader {
 
  /**
   * 建議將此加載程序用於應用程序的發佈版本。 在這種狀況下,應該使用本地JS執行程序。 將從本機代碼中的資源讀取JS包,以節省將大型字符串從java傳遞到本機內存。
   */
  public static JSBundleLoader createAssetLoader(
      final Context context,
      final String assetUrl,
      final boolean loadSynchronously) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        instance.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
        return assetUrl;
      }
    };
  }
 
  /**
   * 此加載程序從文件系統加載包。 將使用本機代碼讀取該包,以節省將大型字符串從java傳遞到本機內存。
   */
  public static JSBundleLoader createFileLoader(final String fileName) {
    return createFileLoader(fileName, fileName, false);
  }
 
  public static JSBundleLoader createFileLoader(
      final String fileName,
      final String assetUrl,
      final boolean loadSynchronously) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        instance.loadScriptFromFile(fileName, assetUrl, loadSynchronously);
        return fileName;
      }
    };
  }
 
  /**
   * 從dev服務器從新加載bundle時使用此加載器。 在這種狀況下,加載器指望預取JS包並存儲在本地文件中。 
   * 咱們這樣作是爲了不在java和本機代碼之間傳遞大字符串,並避免在java中分配內存以適應整個JS包。 
   * 爲了使JS堆棧跟蹤可以正常工做並容許源映射正確地對其進行符號化,須要提供正確的下載bundle的sourceURL。
   */
  public static JSBundleLoader createCachedBundleFromNetworkLoader(
      final String sourceURL,
      final String cachedFileLocation) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        try {
          instance.loadScriptFromFile(cachedFileLocation, sourceURL, false);
          return sourceURL;
        } catch (Exception e) {
          throw DebugServerException.makeGeneric(e.getMessage(), e);
        }
      }
    };
  }
 
  /**
   * 此加載程序用於從開發服務器加載增量包。 咱們將每一個delta消息傳遞給加載器並在C ++中處理它。 
   * 將其做爲字符串傳遞會因爲內存副本而致使效率低下,這必須在後續處理中解決。
   */
  public static JSBundleLoader createDeltaFromNetworkLoader(
    final String sourceURL,
    final NativeDeltaClient nativeDeltaClient) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        try {
          instance.loadScriptFromDeltaBundle(sourceURL, nativeDeltaClient, false);
          return sourceURL;
        } catch (Exception e) {
          throw DebugServerException.makeGeneric(e.getMessage(), e);
        }
      }
    };
  }
 
  /**
   * 啓用代理調試時使用此加載程序。 在這種狀況下,從設備獲取捆綁包是沒有意義的,由於遠程執行器不管如何都必須這樣作。
   */
  public static JSBundleLoader createRemoteDebuggerBundleLoader(
      final String proxySourceURL,
      final String realSourceURL) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        instance.setSourceURLs(realSourceURL, proxySourceURL);
        return realSourceURL;
      }
    };
  }
 
  /** 
   * 加載腳本,返回其加載的源的URL。
   */
  public abstract String loadScript(CatalystInstanceImpl instance);
}

runJSBundle的源碼以下:

@Override
  public void runJSBundle() {
    Log.d(ReactConstants.TAG, "CatalystInstanceImpl.runJSBundle()");
    Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
    // incrementPendingJSCalls();
    mJSBundleLoader.loadScript(CatalystInstanceImpl.this);

    synchronized (mJSCallsPendingInitLock) {

      // Loading the bundle is queued on the JS thread, but may not have
      // run yet.  It's safe to set this here, though, since any work it
      // gates will be queued on the JS thread behind the load.
      mAcceptCalls = true;

      for (PendingJSCall function : mJSCallsPendingInit) {
        function.call(this);
      }
      mJSCallsPendingInit.clear();
      mJSBundleHasLoaded = true;
    }

    // This is registered after JS starts since it makes a JS call
    Systrace.registerListener(mTraceListener);
  }

JSBundleLoader 類中提供了不少種 JSBundle 文件的加載方式,而且能夠看到每種加載方式都是藉助了CatalystInstanceImpl實例來實現。來看 CatalystInstanceImpl 中的具體實現:

/* package */ void loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously) {
    mSourceURL = assetURL;
    jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously);
  }
 
  /* package */ void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {
    mSourceURL = sourceURL;
    jniLoadScriptFromFile(fileName, sourceURL, loadSynchronously);
  }
 
  /* package */ void loadScriptFromDeltaBundle(
    String sourceURL,
    NativeDeltaClient deltaClient,
    boolean loadSynchronously) {
    mSourceURL = sourceURL;
    jniLoadScriptFromDeltaBundle(sourceURL, deltaClient, loadSynchronously);
  }
 
  private native void jniSetSourceURL(String sourceURL);
  private native void jniRegisterSegment(int segmentId, String path);
  private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously);
  private native void jniLoadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously);
  private native void jniLoadScriptFromDeltaBundle(String sourceURL, NativeDeltaClient deltaClient, boolean loadSynchronously);

能夠看到,下面就調用jni層面的代碼CatalystInstanceImpl.cpp裏面的代碼,C++層的代碼咱們不用太關心,只須要知道,通過這一步以後,js和java層面就能夠相互調用類。若是想要看c++的實現,能夠在node_modules的ReactAndroid目錄中查看。

在這裏插入圖片描述

在以前的 runCreateReactContextOnNewThread 方法中,在creatReactContext以後還有一句核心的代碼。

Runnable setupReactContextRunnable = new Runnable() {
  @Override
  public void run() {
    try {
      setupReactContext(reactApplicationContext);
    } catch (Exception e) {
      mDevSupportManager.handleException(e);
    }
  }

ReactContext建立完畢以後,ReactContextInitAsyncTask就會執行onPostExecute中的setupReactContext(reactContext)方法。

private void setupReactContext(final ReactApplicationContext reactContext) {
 
    Log.d(ReactConstants.TAG, "ReactInstanceManager.setupReactContext()");
 
    synchronized (mReactContextLock) {
      mCurrentReactContext = Assertions.assertNotNull(reactContext);
    }
 
    CatalystInstance catalystInstance =
      Assertions.assertNotNull(reactContext.getCatalystInstance());
 
    catalystInstance.initialize();
    mDevSupportManager.onNewReactContextCreated(reactContext);
    mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
 
    // 重置生命週期
    moveReactContextToCurrentLifecycleState();
 
 
    ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
 
    // mAttachedRootViews 保存的是ReactRootView
    synchronized (mAttachedRootViews) {
      for (ReactRootView rootView : mAttachedRootViews) {
        // 將rootview測量並鏈接到 catalystInstance
        attachRootViewToInstance(rootView, catalystInstance);
      }
    }
    ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_END);
 
    ... 代碼省略
  }

attachRootViewToInstance方法會將 rootview 與 catalystInstance 進行綁定。

private void attachRootViewToInstance(
      final ReactRootView rootView,
      CatalystInstance catalystInstance) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
 
    // 獲取 UIManager 
    UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());
 
    // 設置 rootView 
    final int rootTag = uiManagerModule.addRootView(rootView);
    rootView.setRootViewTag(rootTag);
    rootView.runApplication();
   
    UiThreadUtil.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        rootView.onAttachedToReactInstance();
      }
    });
  }
private void attachRootViewToInstance(
      final ReactRootView rootView) {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance");
    
   // 獲取 UIManager 
    UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());



    // 設置 rootView
    final int rootTag = uiManagerModule.addRootView(rootView);
    rootView.setRootViewTag(rootTag);
    rootView.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);
        rootView.onAttachedToReactInstance();
      }
    });
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }

在 attachRootViewToInstance 方法中 設置 rootView tag,並執行 runApplication 方法。

void runApplication() {
      try {
        if (mReactInstanceManager == null || !mIsAttachedToInstance) {
          return;
        }
 
        // 此時 ReactContext 建立已完成
        ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
        if (reactContext == null) {
          return;
        }
 
        // 獲取 catalystInstance
        CatalystInstance catalystInstance = reactContext.getCatalystInstance();
 
        // 將啓動時到初始化參數封裝成 Bundle
        WritableNativeMap appParams = new WritableNativeMap();
 
        appParams.putDouble("rootTag", getRootViewTag());
        @Nullable Bundle appProperties = getAppProperties();
 
        if (appProperties != null) {
          appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
        }
 
        if (getUIManagerType() == FABRIC) {
          appParams.putBoolean("fabric", true);
        }
 
        // 獲取 moduleName, 設置加載狀態
        mShouldLogContentAppeared = true;
 
        String jsAppModuleName = getJSModuleName();
 
        // 調用 catalystInstance 的 getJSModule 方法獲取 AppRegistry,由Java層調用啓動流程入口,執行其中的 runApplication 方法
        catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
 
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
  }

runApplication最終調用的是catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams), AppRegistry.class是JS層暴露給Java層的接口方法。它的真正實如今AppRegistry.js裏,在文章開始時,咱們已經對它進行了簡單介紹,AppRegistry.js 是運行全部RN應用的JS層入口。此時調用JS進行渲染,在經過UIManagerModule將JS組件轉換成Android組件,最終顯示在ReactRootView上。

相關文章
相關標籤/搜索