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<ReactRoot>)中去
      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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279