React Native 代码阅读(一):启动流程(Android)

2019-01-29

介绍

在 Android 工程中集成 React Native 是很容易的,核心代码如下:

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

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

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // The string here (e.g. "MyReactNativeApp") has to match
        // the string in AppRegistry.registerComponent() in index.js
        mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
}

其中主要分为以下几步:

  1. 创建 ReactRootView
  2. 创建 ReactInstanceManager
  3. 两者绑定初始化

还有一点需注意的是:startReactApplication 的第二个参数 "MyReactNativeApp",它与 React Native App 的 index.js 中注册的 Register 名需是一致的。

下面来分别来看。

创建 ReactRootView

Catalyst APP

如果看 ReactRootView 的代码,会发现其中有一个名词——Catalyst APP,催化剂应用。我们只知道有原生应用、React Native 应用,这里的催化剂应用是什么呢?

对此我的理解是,Catalyst 是一个 Framework,它的作用是对 Native 进行改造,以供运行 React Native 应用。这种改造的过程像不像化学中的催化剂,驱使一种物质改变为另一种物质。

构造函数

前文中首先构造 ReactRootView 实例:

mReactRootView = new ReactRootView(this);

因此先看它的构造函数:

public ReactRootView(Context context) {
  super(context);
}

public ReactRootView(Context context, AttributeSet attrs) {
  super(context, attrs);
}

public ReactRootView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
}

我们发现构造中什么都没做,只是进行了默认的 View 初始化工作。

由此可以推测,主要的工作都是在这行进行了:

mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

创建 ReactInstanceManager

ReactInstanceManager

这个类管理 CatalystInstance 的实例。他通过 ReactPackage 对外暴露一种设置 catalyst 实例的方式,并且跟踪实例的生命周期。他还在实例与开发者支持功能之间建立一个关联。

需要建立 ReactInstanceManager 的实例之后才能启动 ReactRootView 中的 JS 应用。

构造者模式

ReactInstanceManager 类中的成员非常多,因此它使用构造者模式进行初始化:

mReactInstanceManager = ReactInstanceManager.builder()
        .setApplication(getApplication())
        .setBundleAssetName("index.android.bundle")
        .setJSMainModulePath("index")
        .addPackage(new MainReactPackage())
        .setUseDeveloperSupport(BuildConfig.DEBUG)
        .setInitialLifecycleState(LifecycleState.RESUMED)
        .build();

其中,传入了:

项目说明
ApplicationApp 的 Application 实例
setBundleAssetNameJS Bundle 文件的名称
setJSMainModulePath在打包服务器中 APP 主模块的路径,这个用于开发过程中重新加载 JS,所有路径都相对于打包程序提供文件的根文件夹。举例:"index.android"、"subdirectory/index.android"
addPackage添加一个 ReactNative 包
setUseDeveloperSupport是否支持开发者调试
setInitialLifecycleState初始生命周期

build()

在上一节中最后调用了 build(),构造出 ReactInstanceManager 实例。

我们先忽略 ReactInstanceManager 构造函数中的细节。

可以简单的理解为:

  • ReactInstanceManager 保存了我们在 Builder 中设置的参数
  • 构造了一个 ReactInstanceManager 实例

绑定

mReactRootView.startReactApplication

我们将重点放在两者的绑定上面:

mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

下面来看 mReactRootView.startReactApplication 的实现,我们只看它的核心逻辑。

主要分为三步:

  1. 参数保存
  2. ReactInstanceManager 初始化
  3. ReactInstanceManager attachRootView

对应的核心代码为:

  1. Step1 参数保存:

    首先把传入的参数保存下来:

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

    注意:

    • 现在 ReactRootView 已经持有 ReactInstanceManager 的实例了。
  2. Step2 初始化 ReactInstanceManager Context

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

    其中:

    • if 判断防止重复初始化 ReactInstanceManager
  3. Step3 ReactInstanceManager attachRootView

    之后调用:

    Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
    

    这句可以简化为:

    mReactInstanceManager.attachRootView(this);
    

    单看这一句,我们大概可以猜出,ReactInstanceManager 很可能也持有了 ReactRootView 的实例,因此他们两者很可能是相互持有的。

ReactInstanceManager Context 初始化

下面我们一步一步来分析上面三步中所调用方法的内部。

ReactInstanceManager 初始化所调用的代码为:

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

我们来看 createReactContextInBackground 中的核心逻辑:

mHasStartedCreatingInitialContext = true;
recreateReactContextInBackgroundInner();

其中:

  • 首先它反转了一个标志位,表示已经开始初始化 Context 了
  • 之后调用了内部方法,我们跟进这个方法

recreateReactContextInBackgroundInner 内部是比较复杂的,主要分为两步:

  • 如何加载包
  • 创建 ReactContext

这里我们先忽略第一步,着重看第二步。加载包的过程将会在后续系列中单独梳理。

跳过加载包的过程,走到这一行代码:

recreateReactContextInBackgroundFromBundleLoader();

我们进入这个方法来看:

recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);

其中:

  • 使用两个成员 mJavaScriptExecutorFactory,mBundleLoader 来调用 recreateReactContextInBackground

我们先来看,这两个成员是如何传入 ReactInstanceManager 的。

暂时回到 ReactInstanceManagerBuilder 中的 build() 方法:

  • 如果我们在 Builder 中没有 set 过 mJavaScriptExecutorFactory,则会创建一个默认 JSCJavaScriptExecutorFactory 实例
  • 如果我们在 Builder 中没有 set 过 mJSBundleLoader,则会创建一个默认的 JSBundleLoader 实例
  • 这两个实例会随构造函数传入 ReactInstanceManager

好了,回到 recreateReactContextInBackground,我们接着看:

首先创建了一个 ReactContextInitParams:

final ReactContextInitParams initParams = new ReactContextInitParams(
    jsExecutorFactory,
    jsBundleLoader);

其中:

  • 我们可以看出 ReactContext 专门有一个 InitParams 类来进行初始化

之后执行:

runCreateReactContextOnNewThread(initParams);

从方法名我们能够看出,这一步会创建 ReactContext,并创建一个新线程。

ReactInstanceManager Context 初始化 2

我们进入 runCreateReactContextOnNewThread 接着看。

这个方法内创建并启动了一个线程:

mCreateReactContextThread =
    new Thread(
        new Runnable() {
            @Override
            public void run() {
                ...
            }
        });
mCreateReactContextThread.start();

其中:

  • 通过线程的构造函数传入一个 Runnable
  • Runnable 会在线程 start 后执行
  • 我们的逻辑都写在 Runnable 里

下面我们来看 Runnable 中的核心逻辑:

首先反转标志位:

mHasStartedCreatingInitialContext = true;

之后创建 ReactContext:

final ReactApplicationContext reactApplicationContext =
    createReactContext(
        initParams.getJsExecutorFactory().create(),
        initParams.getJsBundleLoader());

终于走到了这一步,可以看出,具体的创建过程都在 createReactContext 方法中。我们这里先继续分析 Runnable 逻辑。

在 createReactContext 之后还做了两件事情:

  1. 设置 reactApplicationContext
  2. 向这个主线程再扔一个 Runnable,如果 mPendingReactContextInitParams 不为空,则重新创建 ReactContext

其中,我们主要关注第一步:

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

reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);

其中:

  • 调用 setupReactContext 方法来设置 ReactContext,我们将在后面分析
  • 这个 runnable 是扔到 reactApplicationContext 的 NativeModulesQueueThread 上面去执行
  • 从中我们可以察觉,reactApplicationContext 里面还发起了线程

在下文中,我们先分析 createReactContext 看看 ReactContext 具体是怎么创建的,再分析 setupReactContext。

ReactContext 的创建与绑定

createReactContext

先回顾一下 createReactContext 的方法签名:

private ReactApplicationContext createReactContext(
    JavaScriptExecutor jsExecutor,
    JSBundleLoader jsBundleLoader) {

首先我们直接 new 出了 ReactApplicationContext 实例:

final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);

ReactApplicationContext 我们将在下一节中来看。这里先把 createReactContext 过程分析完。

之后这一步非常非常关键:

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

其中:

  • 我们创建了 NativeModuleRegistry,它是专门管理 Native 包的
  • 包加载的功能我们放在后续文章中集中分析,在本文中,我们还是核心关注 React Native 自身的启动流程

之后创建了 CatalystInstanceImpl 的构造器:

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

...

final CatalystInstance catalystInstance;
try {
    catalystInstance = catalystInstanceBuilder.build();
} finally { ...}

这个类光看它的构造参数就可知道,这是一个很重要的类。

之后是对 CatalystInstance 的一些设置,我们暂且省略,于是来到了这一句:

catalystInstance.runJSBundle();

这一句非常的重要!我们的 JS 包是通过这一句来加载执行的。关于包的运行与加载我们放在后面文章中来梳理,从中可以看出,随着框架的加载,已经开始进入到包的加载与运行阶段了,我们后文主题的线索也开始多起来。

createReactContext 中最后还有一句:

reactContext.initializeWithInstance(catalystInstance);

这个方法调用了 ReactContext.initializeWithInstance。这个方法我们放到下一节,梳理完 ReactApplicationContext 后,再来看它。

ReactApplicationContext

ReactApplicationContext 类本身非常简单,只是对 ReactContext 的简单继承:

public class ReactApplicationContext extends ReactContext {
  public ReactApplicationContext(Context context) {
    super(context.getApplicationContext());
  }
}

我省略了代码中的注释,注释中说到为什么要封装这么一个类。主要是为了在构造函数里面,将传入的 Context 通过 getApplicationContext,保证始终取到的是 Application。

从中也可以看出,ReactContext 才是重头戏。

ReactContext

  1. 介绍

    ReactContext 是 React Native 在 Java 层最核心的一个类。CatalystInstance、消息队列都是它的成员。

  2. 构造函数

    ReactContext 的构造函数是空的:

    public ReactContext(Context base) {
        super(base);
    }
    
  3. initializeWithInstance

    在前文 createReactContext 中最后一句调用的就是 initializeWithInstance,我们现在来看,它是 ReactContext 初始化的核心方法。

    先回忆下方法签名:

    public void initializeWithInstance(CatalystInstance catalystInstance) {
    

    首先我们把它存下来,作为成员变量:

    mCatalystInstance = catalystInstance;
    

    之后我们从 catalystInstance 里面搞出来两个线程:

    ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
    mUiMessageQueueThread = queueConfig.getUIQueueThread();
    mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
    mJSMessageQueueThread = queueConfig.getJSQueueThread();
    

    其中:两个线程是怎么搞出来的? 先看:

    ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
    

    进入 catalystInstance.getReactQueueConfiguration:

    @Override
    public ReactQueueConfiguration getReactQueueConfiguration() {
        return mReactQueueConfiguration;
    }
    

    可以看出,这是一个 getter 方法,mReactQueueConfiguration 是在哪里创建实例的呢?答案是在 CatalystInstanceImpl 的构造函数中:

    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        reactQueueConfigurationSpec,
        new NativeExceptionHandler());
    

    进入 ReactQueueConfigurationImpl.create 再看一下。

    我们简化它的具体细节,简化为 ReactQueueConfigurationImpl 中会创建 3 条线程:

    • uiThread
    • nativeModulesThread
    • jsThread

    回到 initializeWithInstance 的代码:

    ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
    mUiMessageQueueThread = queueConfig.getUIQueueThread();
    mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
    mJSMessageQueueThread = queueConfig.getJSQueueThread();
    

    我们可以看出,ReactContext 也把这三个线程取出来,作为自己的成员变量了。

ReactContext 创建之后

介绍

在前面的小节中,我们创建了 ReactContext,ReactContext 内部又创建了 CatalystInstance,CataLystInstance 内部又创建了多条线程……

ReactContext 创建完成后,框架的初始化过程并没有结束。

回到 ReactRootView 的 startReactApplication 方法,在 createReactContextInBackground 完成之后,方法的末尾还有一句:

attachToReactInstanceManager();

进入到方法中,核心是这一句:

Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);

它可以等同于:

mReactInstanceManager.attachRootView(this);

ReactInstanceManager.attachRootView

我们进入 mReactInstanceManager.attachRootView,来看它的核心逻辑。

首先它把传入的 ReactRootView 存了下来:

mAttachedRootViews.add(rootView);

从中我们可以看出:

  • ReactInstanceManager 是支持多 RootView 的

之后 ReactInstanceManager 把 ReactRootView 给清了:

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

最后,取出 ReactContext,让 ReactRootView 与 CatalystInstance 相关联:

ReactContext currentContext = getCurrentReactContext();
if (mCreateReactContextThread == null && currentContext != null) {
    attachRootViewToInstance(rootView, currentContext.getCatalystInstance());
}

ReactInstanceManager.attachRootViewToInstance

首先通过 UIManagerHelper 拿到 UIManager:

UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());

UIManager 是什么呢?它有两个实现类:

  • UIManager
  • UIManagerModule

我们来看后者,它的注释中说:这是一个原生模块,允许 JS 创建和更新原生视图。

之后,将 rootView 注册进 UIManagerModule 中:

final int rootTag = uiManagerModule.addRootView(rootView);

之后,rootView 会调用下面这句启动 JS 程序:

rootView.invokeJSEntryPoint();

这是一个很重要的点,它是我们 JS Bundle 的执行入口。

最后,我们又向主线程扔了一个 Runnable:

UiThreadUtil.runOnUiThread(new Runnable() {
    @Override
    public void run() {
    ...
    rootView.onAttachedToReactInstance();
    }
});

其中,onAttachedToReactInstance 是我们关心的。

ReactInstanceManager.onAttachedToReactInstance

在这个方法中,主要干了一件事情,就是初始化了触摸事件的分发:

mJSTouchDispatcher = new JSTouchDispatcher(this);
if (mRootViewEventListener != null) {
    mRootViewEventListener.onAttachedToReactInstance(this);
}

结论

至此,我们就把 Android 集成 React Native 框架代码的内部流程完完整整地走了一遍。这一遍走的粒度很粗,其中有很多核心概念都没有深挖。

但是对于初识来说这已经足够了,在本文中,我们弄清了 React Native 的大致启动过程,知道了都有哪些核心组件被创建,也知道了他们的交互过程和时序过程。

这些分析为我们提供了很多线索,供之后进行更加深入地专题学习。