React Native 拆包实践6 - Android 启动流程

完成了iOS的拆包之后,接下来看看Android如何按需加载jsbundle,在此之前同样需要先了解react native应用在android上的启动流程。先从概念入手,再配合源码介绍,一步步找到它加载bundle的方式。

概念

1. ReactContext

ReactContext继承于ContextWrapper,是ReactNative应用的上下文,在官方文档中是这样描述它的:

Abstract ContextWrapper for Android application or activity Context and CatalystInstance

2. CatalystInstance

CatalystInstance是ReactNative中Java层、C++层、JS层通信的管理类,负责Java层、JS层核心Module映射表与回调,是三端通信的入口与桥梁。(ps: catalyst的翻译是一个化学概念:触媒,催化剂。CatalystInstance的作用是协调)

3. ReactInstanceManager

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

4. ReactRootView

应用的UI容器,它本质上是一个FrameLayout,监听size的改变,UI manager可以根据size的更改来重新布局子元素。同时它将所有的touch event通过JSTouchDispatcher发送给JS层。在主Activity的onCreate中最终调用了getPlainActivity().setContentView(mReactRootView);将该view设置为contentView。

/**
 * Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI
 * manager can re-layout its elements. It delegates handling touch events for itself and child views
 * and sending those events to JS by using JSTouchDispatcher. This view is overriding {@link
 * ViewGroup#onInterceptTouchEvent} method in order to be notified about the events for all of its
 * children and it's also overriding {@link ViewGroup#requestDisallowInterceptTouchEvent} to make
 * sure that {@link ViewGroup#onInterceptTouchEvent} will get events even when some child view start
 * intercepting it. In case when no child view is interested in handling some particular touch event,
 * this view's {@link View#onTouchEvent} will still return true in order to be notified about all
 * subsequent touch events related to that gesture (in case when JS code wants to handle that
 * gesture).
 */
public class ReactRootView extends FrameLayout implements RootView, ReactRoot {...}

启动

新创建的ReactNative项目中,MainActivity是整个App的启动入口,这个Activity继承自ReactActivity,并将自己所有的生命周期都代理给ReactActivtiyDelegate类来实现。也就是说onCreate等方法都是将调用ReactActivtiyDelegate类的onCreate等方法。代码如下所示:

public class ReactActivityDelegate {
  private final @Nullable Activity mActivity;
  private final @Nullable String mMainComponentName;
  private @Nullable ReactRootView mReactRootView;
  
  public ReactActivityDelegate(ReactActivity activity, @Nullable String mainComponentName) {
    mActivity = activity;
    mMainComponentName = mainComponentName;
  }

  protected void onCreate(Bundle savedInstanceState) {
    String mainComponentName = getMainComponentName();
    if (mainComponentName != null) {
      loadApp(mainComponentName);
    }
    ...
  }

  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()就是当前的MainActivity
    getPlainActivity().setContentView(mReactRootView);
  }
}

可以看到,在onCreate中,实际的初始化操作发生在loadApp方法中。在此方法中,创建了ReactRootView的实例mReactRootView,在初始化结束时将它设置为MainActivity的contentView。在此之前,还调用了startReactApplication方法,在该方法中完成了几乎所有的初始化工作。

public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle initialProperties) {
  startReactApplication(reactInstanceManager, moduleName, initialProperties, null);
}

该方法,需要三个入参:ReactInstanceManagermoduleName(也就是js中注册的component name) 和initialProperties(启动时,可以通过这个参数传递一些参数到js)。后两个参数没有什么特别,第一个参数正是我们开始时介绍的重要成员之一,它来自于getReactNativeHost().getReactInstanceManager()

  protected ReactNativeHost getReactNativeHost() {
    return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
  }

它是从Application中获得的,App中的Application实现了ReactApplication接口:

public interface ReactApplication {
  ReactNativeHost getReactNativeHost();
}

该接口,只需要提供一个ReactNativeHost。这个类的实现比较简单,当需ReactInstanceManager时,它会创建一个ReactInstanceManager实例

public abstract class ReactNativeHost {

  private final Application mApplication;
  private @Nullable ReactInstanceManager mReactInstanceManager;

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

  public ReactInstanceManager getReactInstanceManager() {
    if (mReactInstanceManager == null) {
      mReactInstanceManager = createReactInstanceManager();
    }
    return mReactInstanceManager;
  }
  
  protected ReactInstanceManager createReactInstanceManager() {
    
    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModulePath(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
      .setUIImplementationProvider(getUIImplementationProvider())
      .setJSIModulesPackage(getJSIModulePackage())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
    // 添加ReactPackage
    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();
    return reactInstanceManager;
  }
}

创建好ReactInstanceManager,我们回到ReactActivityDelegate的loadApp,下要分析的便是startReactApplication方法:

  /**
   * Schedule rendering of the react component rendered by the JS application from the given JS
   * module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the
   * JS context of that manager. Extra parameter {@param launchOptions} can be used to pass initial
   * properties for the react component.
   */
  public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle initialProperties,
      @Nullable String initialUITemplate) {

    try {
      // 省略线程检查的代码
      ...
      mReactInstanceManager = reactInstanceManager;
      mJSModuleName = moduleName;
      mAppProperties = initialProperties;
      mInitialUITemplate = initialUITemplate;
      ...
      // 创建RN应用上下文
      if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
        mReactInstanceManager.createReactContextInBackground();
      }
      // 把自己也就是ReactRootView添加到mReactInstanceManager的mAttachedReactRoots(Set)中去
      attachToReactInstanceManager();
    } finally {
      ...
    }
  }

其中最核心的就是mReactInstanceManager.createReactContextInBackground()

  @ThreadConfined(UI)
  public void createReactContextInBackground() {
    ...
    mHasStartedCreatingInitialContext = true;
    recreateReactContextInBackgroundInner();
  }

由于我们这里只考虑从文件系统中加载jsbundle所以跳过debug的代码分析。

  @ThreadConfined(UI)
  private void recreateReactContextInBackgroundInner() {
    ...
    // 在debug模式下,使用了metrojs,jsbundle将从service中获取
    if (mUseDeveloperSupport && mJSMainModulePath != null) {...}

    // 非debug模式:
    recreateReactContextInBackgroundFromBundleLoader();
  }

  @ThreadConfined(UI)
  private void recreateReactContextInBackgroundFromBundleLoader() {
    ...
    recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
  }

  @ThreadConfined(UI)
  private void recreateReactContextInBackground(
    JavaScriptExecutorFactory jsExecutorFactory,
    JSBundleLoader jsBundleLoader) {

    final ReactContextInitParams initParams = new ReactContextInitParams(
      jsExecutorFactory,
      jsBundleLoader);
    if (mCreateReactContextThread == null) {
      runCreateReactContextOnNewThread(initParams);
    } else {
      mPendingReactContextInitParams = initParams;
    }
  }

  @ThreadConfined(UI)
  private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
    ...
    // 如果mCurrentReactContext非null,则先tearDownReactContext然后将其设置为null

    mCreateReactContextThread = new Thread(null,
            new Runnable() {
              @Override
              public void run() {
                ...
                // 如果当前的reactContext正在销毁当中,则等它销毁了再执行下面的创建操作

                try {
                  ...
                  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);
                }
              }
            },
            "create_react_context");
    mCreateReactContextThread.start();
  }

方法中创建了mCreateReactContextThread线程,并执行了该线程,在该线程中,首先创建了reactApplicationContext,忽略一些非核心代码,并通过官方对ReactContext的描述(它是Android Context以及CatalystInstance的Wrapper),此处创建了ReactContext和CatalystInstance实例。并通过调用runJSBundle方法加载了jsbundle文件。加载jsbundle实际是通过JSBundleLoader,而它有多种,可参考该类中的对应的静态方法createXXXXXXXLoader

  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    ...
    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
      .setJSExecutor(jsExecutor)
      .setRegistry(nativeModuleRegistry)
      .setJSBundleLoader(jsBundleLoader)
      .setNativeModuleCallExceptionHandler(exceptionHandler);
    final CatalystInstance catalystInstance = catalystInstanceBuilder.build();

    if (mJSIModulePackage != null) {
      catalystInstance.addJSIModules(mJSIModulePackage
        .getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder()));
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }
    // 加载jsbundle
    catalystInstance.runJSBundle();
    // 创建的catalystInstance实例赋值给reactContext的内部mCatalystInstance
    reactContext.initializeWithInstance(catalystInstance);
    return reactContext;
  }

此后又创建了两个线程maybeRecreateReactContextRunnable和setupReactContextRunnable。其中maybeRecreateReactContextRunnable同过代码猜测它只是一个补丁,用于修正多线程下可能出现的context创建失败的情况,此处可以先忽略它。在创建好reactContext以及catalystInstance并完成jsbundle的加载后将调用 setupReactContextRunnable,它调用了另一个方法setupReactContext并把创建好的reactApplicationContext传入其中。

  private void setupReactContext(final ReactApplicationContext reactContext) {

    synchronized (mAttachedReactRoots) {
      synchronized (mReactContextLock) {
        mCurrentReactContext = Assertions.assertNotNull(reactContext);
      }

      CatalystInstance catalystInstance =
          Assertions.assertNotNull(reactContext.getCatalystInstance());
      catalystInstance.initialize();
      moveReactContextToCurrentLifecycleState();

      // mAttachedRootViews保存的是ReactRootView
      for (ReactRoot reactRoot : mAttachedReactRoots) {
        attachRootViewToInstance(reactRoot);
      }
    }

    ReactInstanceEventListener[] listeners =
      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
    final ReactInstanceEventListener[] finalListeners =
        mReactInstanceEventListeners.toArray(listeners);

    UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            for (ReactInstanceEventListener listener : finalListeners) {
              listener.onReactContextInitialized(reactContext);
            }
          }
        });

    reactContext.runOnJSQueueThread(
        new Runnable() {
          @Override
          public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
          }
        });
    reactContext.runOnNativeModulesQueueThread(
        new Runnable() {
          @Override
          public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
          }
        });
  }

页面的加载发发生在attachRootViewToInstance中

  private void attachRootViewToInstance(final ReactRoot reactRoot) {
    UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType());
    // 设置rootTag以及从native传入的参数
    @Nullable Bundle initialProperties = reactRoot.getAppProperties();
    final int rootTag = uiManagerModule.addRootView(
      reactRoot.getRootViewGroup(),
      initialProperties == null ?
            new WritableNativeMap() : Arguments.fromBundle(initialProperties),
        reactRoot.getInitialUITemplate());
    reactRoot.setRootViewTag(rootTag);

    // 启动流程入口:由Java层调用启动,它将触发js的runApplication
    reactRoot.runApplication();
    
    UiThreadUtil.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE);
          }
        });
  }

至此也就完成了启动流程的分析,下一节中我们将开始通过自定义的实现来进行按需加载不同的jsbundle。

你可能感兴趣的:(React Native 拆包实践6 - Android 启动流程)