React Native JSBundle 拆分解决方案实践【1】: 应用启动、视图加载原理解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013718120/article/details/79347331

前两篇文章和大家分享了RN中的触摸事件机制。接下来的内容将结合Android原生层来分析拆包解决方案。如果你具有Android原生开发经验,并且在实践 Android + RN 的混合模式开发,那么这次连载的文章内容你一定喜欢。拆包解决方案的系列内容目录大致如下:

(1)RN 应用启动、视图加载原理解析(源码层)

(2)以RN最新版本为例,结合 Metro,分析如何实现拆包

(3)封装原生层 JSBundle 加载逻辑,实现拆包解决方案

要实现RN的拆包,首选需要我们从RN的加载执行流程切入,了解系统是如何完成JS代码、RN视图加载显示的。网上对于启动流程的分析博客很多,这里我们会跟踪代码执行流程,帮大家梳理清楚执行流程即可。在分析启动加载模块之前,大家先想个问题。

一、为什么要拆包?

RN作为非常优秀的移动端跨平台开发框架,在近几年得到众多开发者的认可。国内各大厂采用在当前原生应用内集成RN的方式,使得App应用的灵活性得到了很大的提升。例如:去哪儿、JD、搜车等等,都采用了这种混合开发模式。顾名思义,在原生应用内嵌入RN,就是需要在原生应用内加载RN模块(1个或多个JSBundle),并得以显示。JSBundle中包含了当前RN模块的js代码。如果存在多个RN模块需要被加载时,就需要分别打出多个JSBundle,并且多个JSBundle包含了很多重复的代码(例如:第三方依赖)。拆包的方式,就是将其中重复不变的代码打成基础包,动态变化的打成业务包。那么就做到了JSBundle的拆分。JSBundle的拆分,对降低内存的占用,减少加载时间,减少热更新时流量带宽等,在优化方面起到了非常大的作用。实现拆包的过程涉及的方面比较多,需要我们对RN模块加载,打包过程有非常深刻的理解,所以本篇内容就先从RN的启动加载流程开始。

二、从源码角度解析 RN 应用启动、视图加载原理

源码基于当前最新RN版本:0.57

"dependencies": {
    "react": "16.6.0-alpha.8af6728",
    "react-native": "0.57.3"
}

1. JS端启动流程

创建一个RN项目需要用到 react-native init projectName 命令,当执行完成后,系统会自动生成所需文件,例如 android、ios、index.js等等。index.js 作为应用的默认入口,需要将当前APP注册到AppRegistry组件中:

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

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

我们来看在 node_modules/react-native/Libraries/ReactNative 目录下的 AppRegistery.js 文件:

/**
 * 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;
},

registerComponent 方法中传入了 appKey,ComponentProvider 参数并调用了 renderApplication 方法。可以发现在方法中,执行了runnables,并拿到了 appParameters,该参数即从原生层在应用加载初始化时传递给RN层的参数。在分析Native层源码时,我们再仔细看。

 renderApplication 和AppRegistry 文件在同一目录下。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.
   *
   */
  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 方法中,通过 runnables[appKey] && runnables[appKey].run 来检查是否可以找到appKey对应的module组件,如果没有,则会抛出异常。

视图是如何被加载显示呢?就需要我们从Native层一探究竟。

2. Native端启动流程

当RN项目创建完成后,系统会生成android、ios平台的源码。打开android目录,可以看到在原生代码中会生成 MainActivity和 MainApplication 两个Java类。很明显没,MainActivity 即为原生层应用程序的入口文件。MainApplication作为整体应用程序的初始化入口文件。我们先来看 MainActivity.java 文件:

public class MainActivity extends ReactActivity {

    /**
     * 返回你在rn index.js 注册的名称,
     * 即 AppRegistry.registerComponent()传入的名称,用来渲染组件。
     */
    @Override
    protected String getMainComponentName() {
        return "splitBundle";
    }
}

很简单,继承 ReactActivity 并实现 getMainComponentName 方法,返回与 AppRegistry.registerComponent 的 appKey 相同名称即可。继续来看 MainApplication.java 文件:

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();
    // 加载C++层渲染代码
    SoLoader.init(this, /* native exopackage */ false);
  }
}

MainApplication 中主要完成了三件事:

(1)实现 ReactApplication 接口,重写 getReactNativeHost 方法,返回ReactNativeHost实例。

(2)定义并初始化 ReactNativeHost,实现 getUseDeveloperSupport、getPackages、getJSMainModuleName 方法,完成初始化设置。

(3)在 onCreate 生命周期方法中,调用SoLoader的init方法,启动C++层逻辑代码的初始化加载。

了解完两个文件,我们先从 MainActivity 开始分析。

ReactActivity

MainActivity 继承 ReactActivity 类,重写了getMainComponentName方法,并且方法的返回值需要和我们在JS端的值保持一致。通过上面我们分析的 AppRegistry.js 中的 runApplication 方法发现,如果getMainComponentName中返回的名称与 RN 层AppRegistry.registerComponent注册名称不一致,会出现 Application XXX appKey has not been registered 异常。跟进 ReactActivity 类,看下核心代码:


package com.facebook.react;


/**
 * Base Activity for React Native applications.
 */
public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {

  private final ReactActivityDelegate mDelegate;

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

  /**
   * 返回从JavaScript注册的主要组件的名称,用于安排组件的渲染。
   * e.g. "MoviesApp"
   */
  protected @Nullable String getMainComponentName() {
    return null;
  }

  /**
   * 在构造时调用,如果您有自定义委托实现,则覆盖.
   */
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName());
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mDelegate.onCreate(savedInstanceState);
  }

  ... 中间省略生命周期、返回事件、权限申请函数


  /**
   * 获取 ReactNativeHost 实例
   */
  protected final ReactNativeHost getReactNativeHost() {
    return mDelegate.getReactNativeHost();
  }

  /**
   * 获取 ReactInstanceManager 实例
   */
  protected final ReactInstanceManager getReactInstanceManager() {
    return mDelegate.getReactInstanceManager();
  }

  /**
   * 加载 JSBundle
   */
  protected final void loadApp(String appKey) {
    mDelegate.loadApp(appKey);
  }
}

ReactActivity类中主要定义如下:

(1)继承 Activity,实现 DefaultHardwareBackBtnHandler、PermissionAwareActivity 两个接口。重写其中的返回事件,及请求权限的方法。

(2)构造函数中调用 createReactActivityDelegate 方法,传入this、和 getMainComponentName 方法返回值,创建 ReactActivityDelegate实例。

(3)重写 Activity 生命周期方法,调用 delegate 实例的对应生命周期方法。

(4)定义获取 ReactNativeHost、ReactInstanceManager 实例方法。

(5)定义 loadApp方法。

很明显,ReactActivity 中采用了委托的方式,将所有行为全权交给了 ReactActivityDelegate 去处理。好处也很明显,降低代码耦合,提升了可扩展能力。我们接着来看 ReactActivityDelegate 类中核心代码是如何定义的。

ReactActivityDelegate


package com.facebook.react;


/**
 * Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this
 * to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application
 * class doesn't implement {@link ReactApplication}.
 */
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 类中主要定义如下:

(1)定义 getReactNativeHost、getReactInstanceManager 方法,获取对应实例。可以看到此处调用的是MainApplication中定义的ReactNativeHost实例。这里也就明白,为什么MainApplication需要实现ReactApplication接口,并重写 getReactNativeHost方法了。如果不实现 ReactApplication,可以把ReactNativeHost定义成全局静态常量即可。

(2)定义生命周期方法、返回事件方法、权限请求方法。并在方法中调用 ReactInstanceManager 中对应的方法。

(3)定义 createRootView 方法,在该方法中,通过 new ReactRootView,创建 ReactRootView 实例。

(4)在 onCreate 生命周期中,判断 mMainComponentName,如果不为 null,则执行 loadApp 方法。所以我们重点来看 loadApp 方法。

(5)在 loadApp 方法中主要做了三件事:

         1. 创建 RootView

         2. 调用 RootView 实例的 startReactApplication 方法,将 ReactInstanceManager 实例、appKey、启动时初始化参数作为参数传递过去

         3. 将 ReactRootView 设置为 MainActivity 布局视图

ReactNativeHost

从 ReactActivityDelegate 方法中,我们了解到很多方法都交给了 ReactInstanceManager 实例去处理,ReactInstanceManager实例时通过 MainApplication 类中初始化的 ReactNativeHost 实例获取,继续跟进 ReactNativeHost 源码

package com.facebook.react;


/**
 * 包含 ReactInstanceManager 实例的简单类, 在 MainApplication 中定义 或 定义成静态字段使用。
 */
public abstract class ReactNativeHost {

  private final Application mApplication;
  private @Nullable ReactInstanceManager mReactInstanceManager;

  protected ReactNativeHost(Application application) {
    mApplication = application;
  }

  /**
   * 获取 或 创建 ReactInstanceManager 实例
   */
  public ReactInstanceManager getReactInstanceManager() {
    if (mReactInstanceManager == null) {
      mReactInstanceManager = createReactInstanceManager();
    }
    return mReactInstanceManager;
  }

  /**
   * 获取此持有者是否包含{@link ReactInstanceManager}实例
   */
  public boolean hasInstance() {
    return mReactInstanceManager != null;
  }

  /**
   * 销毁当前实例并释放对其的内部引用,允许它进行GCed
   */
  public void clear() {
    if (mReactInstanceManager != null) {
      mReactInstanceManager.destroy();
      mReactInstanceManager = null;
    }
  }
  
  /**
   * 创建 ReactInstanceManager 实例
   */
  protected ReactInstanceManager createReactInstanceManager() {
    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModulePath(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
      .setJSIModulesPackage(getJSIModulePackage())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

    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();
    return reactInstanceManager;
  }


  protected final Application getApplication() {
    return mApplication;
  }

  protected @Nullable
  JSIModulePackage getJSIModulePackage() {
    return null;
  }

  /**
   * 返回 JSBundle 主模块的名称。 确定用于获取JS包的URL来自打包服务器。 
   * 它仅在启用dev支持时使用。
   * 这是创建 ReactInstanceManager 后要执行的第一个文件。
   * 默认 index.android”
   */
  protected String getJSMainModuleName() {
    return "index.android";
  }

  /**
   * 返回捆绑文件的自定义路径。 这是在应该加载bundle的情况下使用的自定义路径。例如“文件://sdcard/myapp_cache/index.android.bundle”
   * 默认情况下,它是从指定路径的Android assets目录下加载
   */
  protected @Nullable String getJSBundleFile() {
    return null;
  }

  /**
   * 返回资产中包的名称。 如果为null,则不指定文件路径捆绑。
   * 该方法只能与 getUseDeveloperSupport 一起使用并且将会总是尝试从打包服务器加载JS包。
   * 默认为 index.android.bundle
   */
  protected @Nullable String getBundleAssetName() {
    return "index.android.bundle";
  }

  /**
   * 返回是否应启用dev模式
   */
  public abstract boolean getUseDeveloperSupport();

  /**
   * 返回应用程序使用的 ReactPackage 列表,至少返回 MainReactPackage。
   * 如果您的应用使用除默认视图或模块之外的其他视图或模块,您需要在此处添加更多套餐。
   */
  protected abstract List<ReactPackage> getPackages();
}

在创建 ReactInstanceManager 过程中,设置了JSMainModuleName、是否开发者模式、初始化的 Packages、JSBundleFile,并且系统为我们提供了更多的选择来自定义行为方式,例如JSBundle的加载路径,JSBundle文件名称等等。此时再回去看MainApplication中定义ReactNativeHost的代码,相信你已经恍然大悟,其实就是在为创建ReactInstanceManager 做热身准备。

ReactRootView

ReactRootView是一个自定义的View,其父类是FrameLayout。在 ReactActivityDelegate 类的 loadApp方法中,调用了ReactRootView实例的 startReactApplication 方法 ,我们来看 startReactApplication 方法中做了什么。

  /**
   * 使用提供的{@param reactInstanceManager}通过(@ {param 
   * moduleName})加载并呈现的react组件的渲染,同时附加到该管理器的JS上下文。 
   * 额外参数{@param launchOptions}可用于传递react组件的initialproperties。
   */
  public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle initialProperties) {
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
    try {
      UiThreadUtil.assertOnUiThread();
      Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");

      mReactInstanceManager = reactInstanceManager;
      mJSModuleName = moduleName;
      mAppProperties = initialProperties;

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

      attachToReactInstanceManager();

    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
  }

在 startReactApplication 方法中调用了 ReactInstanceManager 实例的 createReactContextInBackground 方法。

  
  /**
   * 后台异步任务中异步响应上下文初始化。 这使应用程序能够预加载应用程序JS,并在ReactRootView可用和测量之前执行全局代码。 
   * 仅在应用程序第一次设置时从UI线程调用
   * 只在创建反应时调用 createReactContextInBackground。当重新加载JS时,例如从新文件中,应使用 recreateReactContextInBackground
   */
  @ThreadConfined(UI)
  public void createReactContextInBackground() {

    mHasStartedCreatingInitialContext = true;
    recreateReactContextInBackgroundInner();
  }

该方法只会在 Application 中执行一次,JSBundle 重载时,会走 recreateReactContextInBackground, 这两个方法最终都会调用recreateReactContextInBackgroundInner 方法。

  @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文件。

  @ThreadConfined(UI)
  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 方法:

  @ThreadConfined(UI)
  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();
  }

在 runCreateReactContextOnNewThread 方法中,首先创建调用 createReactContext 创建 ReactApplicationContext,然后开启线程执行。重点来看 createReactContext 方法

CatalystInstance

/**
   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
   */
  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;
  }

createReactContext 方法比较长,主要做了3件事:

(1)构建 ReactApplicationContext

(2)注册 Packages 原生模块

(3)构建 CatalystInstance 实例

(4)通过 CatalystInstance 实例调用C++层代码逻辑

(5)调用 CatalystInstance 实例的 runJSBundle 方法加载 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;
    }

    // 这是在 JS 启动后注册的,因为它进行了 JS 调用
    Systrace.registerListener(mTraceListener);
  }

在 runJSBundle 方法中通过JSBundleLoader的 loadScript 方法去加载JSBundle。不同的加载方式 JSBundleLoader 实现方式不同。

JSBundleLoader

package com.facebook.react.bridge;

import android.content.Context;
import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.common.DebugServerException;

/**
 * 一个存储 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);
}

JSBundleLoader 类中提供了很多种 JSBundle 文件的加载方式。

猜你喜欢

转载自blog.csdn.net/u013718120/article/details/79347331