本章主要来探讨一下,RN 的启动过程都做了什么?同时简单的介绍下在 Android 中是如何实现 ReactNative 的。进而引出解决一个重要的问题,ReactNative 的预加载。
ReactNative 系统框架概述
ReactNative 源码结构图如下:
其中几个主要内容:
- Libraries:JS层的实现,实现了JS组件的封装与JS队列的封装
- ReactAndroid:Android 源码实现
- ReactCommon:C++ 层实现,实现了对脚本引擎JSC的封装与通信桥ReactBridge,Android与iOS调用
- React:ReactNative源码的主要内容
ReactNative 主要工作就两部分:
第一部分实现:ReactNative 应用启动流程;ReactNative应用UI的绘制与渲染;ReactNative应用通信机制;ReactNative应用线程模型
第二部分:ReactNative运行时的异常以及异常的捕获与处理;SOLoader加载动态链接库;ReactNative触摸事件处理机制。
我们先从一个 Demo 工程来看 ReactNative 启动流程
启动流程
应用初始化
首先,我们打开这个普通工程的 android
目录,这里就是一个完整的 android 项目。
- 首先我们看
MainApplication.java
里面的 RN 的初始化操作
|
|
ReactApplication
可以看到我们在Application里实现了 ReactApplication接口,该接口要求创建一个ReactNativeHost对象。ReactNativeHost 对象,本身持有 ReactInstanceManager
对象。其对外暴露两个需要实现的方法:
|
|
ReactNativeHost主要的工作就是创建 ReactInstanceManager,创建部分代码如下:
|
|
- 接下来看 MainActivity.java
|
|
这里我们的 MainActivity 继承自 ReactActivity
,ReactActivity作为JS页面的容器。最后我们的前端界面的内容,就是渲染到了这个容器上面。(ReactActivity 后面介绍)
应用启动流程
我们开始分析 ReactActivity
。ReactAcivity
本身继承自 Activity,并实现了其生命周期。本质上其什么也没有做,都是委托给了 ReactActivityDelegate
。代码如下:
|
|
其本身所有行为都交给了 ReactActivityDelegate
来处理,我们只需要关心 ReactActivityDelegate
即可。
ReactActivityDelegate
我们先来看,ReactActivity 的 onCreate
方法,本质上映射到了 ReactActivityDelegate
的 onCreate。
ReactActivityDelegate.onCreate(Bundle savedInstanceState)
|
|
创建阶段主要了做了如下几个事情:
- 创建ReactRootView作为应用的容器,它本质上是一个FrameLayout。
- 调用ReactRootView.startReactApplication()进一步执行应用启动流程。
- 调用Activity.setContentView() 将创建的 ReactRootView 作为 ReactActivity的content view。
所以呢,RN 其实被渲染到了一个 ReactRootView
上面,他可以被用在 Android 任何地方(比如 RN 视图和 原生视图组合)。接下来我们看启动过程 startReactApplication
。
ReactRootView.startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions)
简要代码如下:
|
|
参数信息:
- ReactInstanceManager reactInstanceManager:ReactInstanceManager 实例
- String moduleName:模块的名字,对应ReactActivity.getMainComponentName()与AppRegistry.registerComponent()
- Bundle launchOptions:Bundle 类型,可以在 startActivity 时候传递参数到 JS 层
(UiThreadUtil 主要包装了两个方法:UiThreadUtil.isOnUiThread(),UiThreadUtil.runOnUiThread(Runnable runnable))
主要还是调用了 ReactInstanceManager 上的 createReactContextInBackground
方法。
ReactInstanceManager.createReactContextInBackground()
|
|
这里创建过程从上到下执行,最后调用到 runCreateReactContextOnNewThread
。该方法实际上就是新启了一个线程,来执行如下内容:
|
|
- 主要创建 JavaModule 表,交给 CatalystInstance 管理
- 处理ReactPackage,将JavaModule与JavaScriptModule放进各自对应的注册表里。
- 通过上面jsExecutor、nativeModuleRegistry、jsModulesRegistry、jsBundleLoader、exceptionHandler等参数创建CatalystInstance实例。
- 关联 ReactContext 与 CatalystInstance,并将JS Bundle加载进来,等待ReactContextInitAsyncTask结束以后调用JS入口渲染页面。
- 最后调用 CatalystInstance.runJSBundle()去加载 JS Bundle
最终由 C++ 中的JSCExecutor.cpp 完成了 JS Bundle 的加载。
这里起到作用的就是 CatalystInstance
他由 CatalystInstanceImpl 构造而成。查看代码:
CatalystInstanceImpl
|
|
- ReactCallback callback:CatalystInstanceImpl的静态内部类ReactCallback,负责接口回调。
- JavaScriptExecutor jsExecutor:JS执行器,将JS的调用传递给C++层。
- MessageQueueThread jsQueue.getJSQueueThread():JS线程,通过mReactQueueConfiguration.getJSQueueThread()获得,
- Collection
javaModules:java modules,来源于mJavaRegistry.getJavaModules(this)。 - Collection
cxxModules):c++ modules,来源于mJavaRegistry.getCxxModules()。
CatalystInstanceImpl 创建好,调用 runJSBundle 来加载js
CatalystInstanceImpl.runJSBundle()
这个代码最后会调用,初始化创建 ReactInstanceManager -> createReactContext 传入的 JSBundleLoader bundleLoader
上的
|
|
bundleLoader 由 ReactInstanceManagerBuilder.setJSBundleFile(String jsBundleFile)
创建而来
|
|
JSBundleLoader 提供了多种加载方案,通过 ReactInstanceManagerBuilder.setJSBundleFile(String jsBundleFile)
调用的加载器如下:
|
|
可以看到,在这种加载器下,最后调用的是 CatalystInstanceImpl
上的 loadScriptFromFile
|
|
CatalystInstanceImpl.java 最终还是调用C++层的 CatalystInstanceImpl.cpp去加载JS Bundle。
接下来就是 C++
部分了,不太会了呢。
ReactInstanceManager.setupReactContext(final ReactApplicationContext reactContext)
当 createContext
调用完毕后,C++ 会回调了 setupReactContextRunnable
线程,该线程调用的就是 setupReactContext
方法。代码如下
|
|
ReactInstanceManager.attachRootViewToInstance
方法,重置 ReactRootView
内容,然后将ReactRootView作为根布局,作为根布局进行绘制。随后调用 rootView.setRootViewTag(rootTag);
设置内相关内容,调用 rootView.runApplication()
启动 js。
rootView.runApplication
里面就是包装了一些启动参数 launchOptions 与 模块名 jsAppModuleName。然后最终调用了
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams)
方法:
检查下这个代码:
|
|
根据注释我们知道这有可能就是js层暴露给java的接口方法。?? (AppRegister.js)