背景
公司有一部分业务是使用react-native的开发,了解源码中的启动流程有助于我们更好得进行开发
本文以react-native@0.65.1的Android版本进行研究
项目入口
通过rn-cli新建一个项目,我们可以查看到主要有两个文件:MainActivity, MainApplication

MainApplication

对于MainApplication来说。主要有两点需要关注
- onCreate的时候初始化了关于c++层加载解析的库
SoLoader - 持有ReactNativeHost,查看reactNativeHost如下
主要作用是保存当前的app的应用实例,并且从这其中能够新建一个reactInstanceManager,将一些初始化的参数传进去,可以理解为一个中转站

这里在去到builder文件里查看

赋值了默认的js excutor以及jsbundleLoader默认使用createAssetLoader,这里是个小伏笔,后面会用到
这里暂不先去看ReactInstanceManage里面做了什么。因为这里还没有方法的调用
到这里MainApplication就初始化完成了
接下来就去看MainActivity
MainActivity

MainActivity继承自我们的关键成员之一ReactActivity
java层关键代码
ReactActivity
ReactActivity实际上只是个挂名activity,其间关于rn应用的操作都通过调用ReactActivityDelegate进行。

接下来看看ReactActivityDelegate的create做了什么

这里主要初始化了reactDelegate的实例,并且将当前的activity以及一些初始化参数传了进去,然后调用了reactDelegate的loadApp方法

可以看到loadApp方法主要就是调用了刚刚提到的另一个关键成员ReactRootView
的startApplication的方法,这里应该就是关键的启动流程了
总结一下Delegate这块的调用,主要就是三步
- 创建reactRootView作为承载rn应用的view
- 调用ReactRootView的startRactApplication去执行rn应用的启动流程
- 将创建的reactRootView设置到activity中
接下来看看ReactRootView
ReactRootVIew

ReactRootView这里有一个注释总结了ReactRootView的作用
主要就是作为rn应用的根试图为元素作布局,同时监听一些原生事件给到js那边去处理
关注一下他的startApplication方法

这里在确保当前运行在ui线程后,调用了reactInstanceManager
的createReactContextInBackground方法
ReactContext
最终实际上是调用到了recreateReactContextInBackground

runCreateReactContextOnNewThread的主要逻辑如下

主要任务就是初始化rn上下文,并将他设置好。
先查看其第一步,createReactContext


这里出现了刚刚的另一个核心成员通信桥梁 catalysInstanceImpl
这里主要做了几件事情
- 创建JavaModule注册表
- 创建好
catalysInstanceImpl实例 - 如果存在jsiModule,也一起注册了
- 开始加载js bundle
先来查看一下CatalysInstanceImpl的构造方法
CatalysInstanceImpl

查看CatalysInstanceImpl的构造函数,这里主要有两个关键点
- 初始化c++的方法
- 初始化桥,实际上是调用的native的方法(这里在后面通信部分详细解读)
我们先继续看启动流程,查看runJSBundle

得知实际上也是调用了jsbundleLoader的loadScript方法
进入jsbundleLoader

看到有好几种loader,想起了前面埋下的那个伏笔,这里应该是用的createAssetLoader, 其间的loadScript
实际上是调用了CatalysInstanceImpl的loadScriptFromAssets方法

这里实际上也是调用了原生c++方法,
综上,在这里实际上catalysInstanceImpl初始化后就调用了loadjs的方法
先画一个当前的流程图
java层总结

c++层初始化
进入CatalysInstaneImpl.cpp
看到这里注册了一系列方法,里面有刚刚Java层调用的initializeBridge和jniLoadScriptFromAssets

我们还是先看loadScript的处理

继续,最终通过调用链路instance::loadScriptFromString → loadBundle → nativeToJsBridge → JSIExcutor
调用到loadbundle



会有两步执行,第一步就是通过jscore直接执行javascript的脚本,也即我们打包出来的bundle入口文件
第二步是执行bindBrdige,这一步我们也先忽略,后面通信部分会讲,先记住这个方法名 bindBridge
第一步是执行bundle入口文件
回到我们熟悉的js代码,入口文件

实际上调用了appRegistry.registerComponent

registerComponent实际上只是把应用的rederApplication方法的调用以appKey为索引存储在了runnables中, 看起来还是需要有一个地方去调用renderApplication
我们继续看java层的流程,前面提到在创建完RN上下文后会进行setup,接下来看看setup主要做了什么
执行render
js执行完毕后,回到java层的ReactInstanceManager接下来执行了什么
在setupReactContext中会调用attachRootViewToInstance


这里继续查看

可喜的发现,在这里会找到appRegistry并调用其的runApplication方法
先忽略getJSModuel这个方法,这里实际上也是bridge的调用(后面讲bridge native调用js会提到)

到了rn这里就是通过在runnable中找到对应的comp去执行renderApplication了
到这里整个启动流程就结束了
接下来就是js端的执行,计算虚拟dom。最终再通过bridge交由原生模块渲染
总结
总体的流程图应该是这样的

总结下总体的流程
- ReactActivity持有ReactInstanceManager,并且设置了ReactRootView
- 然后通过ReactRootView去调用Manager创建Rn的上下文
- 在这里会实例化CatalysInstanceImpl,CatalysInstanceImpl会注册需要暴露的原生模块以及要调用的js模块
- 然后就会调用loadJs方法以及initialBridge,初始化桥在原生部分的代码
- 然后这里就进入了C++层
- 在C++层加载bundle并通过js引擎去执行bundle
- 加载进来的bundle就是我们打包的入口文件
- 在bundle这一层就是利用appRegistry去注册我们的component以及render的方法
- 然后在load完js后java层会调用renderApplication,找到runnable中的函数并执行,最终渲染。这里会涉及到一点桥的通信,后面会提到。(敬请期待下一篇)
参考文档: