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(); } }
其中主要分为以下几步:
还有一点需注意的是:startReactApplication 的第二个参数 "MyReactNativeApp",它与 React Native App 的 index.js 中注册的 Register 名需是一致的。
下面来分别来看。
如果看 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);
这个类管理 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();
其中,传入了:
项目 | 说明 |
---|---|
Application | App 的 Application 实例 |
setBundleAssetName | JS Bundle 文件的名称 |
setJSMainModulePath | 在打包服务器中 APP 主模块的路径,这个用于开发过程中重新加载 JS,所有路径都相对于打包程序提供文件的根文件夹。举例:"index.android"、"subdirectory/index.android" |
addPackage | 添加一个 ReactNative 包 |
setUseDeveloperSupport | 是否支持开发者调试 |
setInitialLifecycleState | 初始生命周期 |
在上一节中最后调用了 build(),构造出 ReactInstanceManager 实例。
我们先忽略 ReactInstanceManager 构造函数中的细节。
可以简单的理解为:
我们将重点放在两者的绑定上面:
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
下面来看 mReactRootView.startReactApplication 的实现,我们只看它的核心逻辑。
主要分为三步:
对应的核心代码为:
Step1 参数保存:
首先把传入的参数保存下来:
mReactInstanceManager = reactInstanceManager; mJSModuleName = moduleName; mAppProperties = initialProperties;
注意:
Step2 初始化 ReactInstanceManager Context
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); }
其中:
Step3 ReactInstanceManager attachRootView
之后调用:
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
这句可以简化为:
mReactInstanceManager.attachRootView(this);
单看这一句,我们大概可以猜出,ReactInstanceManager 很可能也持有了 ReactRootView 的实例,因此他们两者很可能是相互持有的。
下面我们一步一步来分析上面三步中所调用方法的内部。
ReactInstanceManager 初始化所调用的代码为:
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); }
我们来看 createReactContextInBackground 中的核心逻辑:
mHasStartedCreatingInitialContext = true; recreateReactContextInBackgroundInner();
其中:
recreateReactContextInBackgroundInner 内部是比较复杂的,主要分为两步:
这里我们先忽略第一步,着重看第二步。加载包的过程将会在后续系列中单独梳理。
跳过加载包的过程,走到这一行代码:
recreateReactContextInBackgroundFromBundleLoader();
我们进入这个方法来看:
recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
其中:
我们先来看,这两个成员是如何传入 ReactInstanceManager 的。
暂时回到 ReactInstanceManagerBuilder 中的 build() 方法:
好了,回到 recreateReactContextInBackground,我们接着看:
首先创建了一个 ReactContextInitParams:
final ReactContextInitParams initParams = new ReactContextInitParams( jsExecutorFactory, jsBundleLoader);
其中:
之后执行:
runCreateReactContextOnNewThread(initParams);
从方法名我们能够看出,这一步会创建 ReactContext,并创建一个新线程。
我们进入 runCreateReactContextOnNewThread 接着看。
这个方法内创建并启动了一个线程:
mCreateReactContextThread = new Thread( new Runnable() { @Override public void run() { ... } }); mCreateReactContextThread.start();
其中:
下面我们来看 Runnable 中的核心逻辑:
首先反转标志位:
mHasStartedCreatingInitialContext = true;
之后创建 ReactContext:
final ReactApplicationContext reactApplicationContext = createReactContext( initParams.getJsExecutorFactory().create(), initParams.getJsBundleLoader());
终于走到了这一步,可以看出,具体的创建过程都在 createReactContext 方法中。我们这里先继续分析 Runnable 逻辑。
在 createReactContext 之后还做了两件事情:
其中,我们主要关注第一步:
Runnable setupReactContextRunnable = new Runnable() { @Override public void run() { try { setupReactContext(reactApplicationContext); } catch (Exception e) { mDevSupportManager.handleException(e); } } }; reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
其中:
NativeModulesQueueThread
上面去执行在下文中,我们先分析 createReactContext 看看 ReactContext 具体是怎么创建的,再分析 setupReactContext。
先回顾一下 createReactContext 的方法签名:
private ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
首先我们直接 new 出了 ReactApplicationContext 实例:
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
ReactApplicationContext 我们将在下一节中来看。这里先把 createReactContext 过程分析完。
之后这一步非常非常关键:
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
其中:
之后创建了 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 类本身非常简单,只是对 ReactContext 的简单继承:
public class ReactApplicationContext extends ReactContext { public ReactApplicationContext(Context context) { super(context.getApplicationContext()); } }
我省略了代码中的注释,注释中说到为什么要封装这么一个类。主要是为了在构造函数里面,将传入的 Context 通过 getApplicationContext,保证始终取到的是 Application。
从中也可以看出,ReactContext 才是重头戏。
介绍
ReactContext 是 React Native 在 Java 层最核心的一个类。CatalystInstance、消息队列都是它的成员。
构造函数
ReactContext 的构造函数是空的:
public ReactContext(Context base) { super(base); }
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 条线程:
回到 initializeWithInstance 的代码:
ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration(); mUiMessageQueueThread = queueConfig.getUIQueueThread(); mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread(); mJSMessageQueueThread = queueConfig.getJSQueueThread();
我们可以看出,ReactContext 也把这三个线程取出来,作为自己的成员变量了。
在前面的小节中,我们创建了 ReactContext,ReactContext 内部又创建了 CatalystInstance,CataLystInstance 内部又创建了多条线程……
ReactContext 创建完成后,框架的初始化过程并没有结束。
回到 ReactRootView 的 startReactApplication 方法,在 createReactContextInBackground 完成之后,方法的末尾还有一句:
attachToReactInstanceManager();
进入到方法中,核心是这一句:
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
它可以等同于:
mReactInstanceManager.attachRootView(this);
我们进入 mReactInstanceManager.attachRootView,来看它的核心逻辑。
首先它把传入的 ReactRootView 存了下来:
mAttachedRootViews.add(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()); }
首先通过 UIManagerHelper 拿到 UIManager:
UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType());
UIManager 是什么呢?它有两个实现类:
我们来看后者,它的注释中说:这是一个原生模块,允许 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 是我们关心的。
在这个方法中,主要干了一件事情,就是初始化了触摸事件的分发:
mJSTouchDispatcher = new JSTouchDispatcher(this); if (mRootViewEventListener != null) { mRootViewEventListener.onAttachedToReactInstance(this); }
至此,我们就把 Android 集成 React Native 框架代码的内部流程完完整整地走了一遍。这一遍走的粒度很粗,其中有很多核心概念都没有深挖。
但是对于初识来说这已经足够了,在本文中,我们弄清了 React Native 的大致启动过程,知道了都有哪些核心组件被创建,也知道了他们的交互过程和时序过程。
这些分析为我们提供了很多线索,供之后进行更加深入地专题学习。