- 我们从源码出发,分析下RN72的启动过程
- 整个过程比较清晰,先启动终端运行时,随后由终端上下文去启动JS的运行时,进而布局,最后再由终端进行渲染,最后将View添加到RootView上
-
启动终端运行时(原生应用启动):
- 用户点击应用图标或从后台恢复应用时,原生应用(Android或iOS)开始启动。 - 系统加载并初始化原生应用的各个部分,包括启动Activity(Android)或App Delegate(iOS)。
-
加载和初始化原生模块:
- 原生应用加载并初始化必要的原生模块,这些模块提供了访问设备特定功能(如网络、文件系统、相机等)的接口。
-
启动JS的运行时:
- 原生代码创建一个JavaScript运行时环境(如JavaScriptCore、V8或Hermes)。
- JavaScript包(通常是一个bundle文件,如
index.bundle)被加载到JavaScript运行时中。这个文件包含了整个React Native应用的JavaScript代码。
-
初始化React Native桥(ReactBridge):
- React Native桥(也称为Native Modules)在此时被初始化,并建立JavaScript和原生代码之间的通信通道。
- 桥允许JavaScript代码调用原生模块的功能,并接收来自原生代码的事件。
-
加载和渲染React组件(布局):
- 在JavaScript代码中,React组件被加载并渲染。这通常从入口文件(如
App.js)开始,然后递归地渲染其子组件。 - React的虚拟DOM被创建,并根据组件的状态和属性进行布局计算。
- 在JavaScript代码中,React组件被加载并渲染。这通常从入口文件(如
-
创建和更新原生视图:
- React Native的渲染器(如
UIManager)遍历虚拟DOM,并生成对应的原生视图层次结构。 - 对于React Native的核心组件,渲染器会创建相应的原生视图(如
UIView、TextView、ImageView等,在iOS上)或(如ViewGroup、TextView、ImageView等,在Android上)。 - 对于自定义的React Native组件,渲染器会调用相应的原生模块来创建和更新视图。
- React Native的渲染器(如
-
将视图添加到RootView:
- 原生代码有一个RootView,它是整个React Native视图层次结构的根容器。
- 当React Native的渲染器完成视图层次结构的创建后,它会将这些视图添加到RootView中。
-
开始渲染并显示UI:
- 一旦视图被添加到RootView中,它们就会被原生系统渲染并显示在屏幕上。
- 如果在后续过程中React组件的状态发生变化,React Native的渲染器会重新计算虚拟DOM,并仅更新需要变化的原生视图,以实现高效的UI更新。
-
这个流程展示了React Native应用从原生层启动,到JavaScript层运行,再到通过React Native桥与原生层交互,并最终渲染出UI界面的整个过程。每个步骤都依赖于前一个步骤的成功完成,从而确保了整个应用的顺利启动和运行
-
-
名词解释
- 模块
- 模块即暴露给调用方的API集合 - - 一种是Native层暴露给Js层的API集合模块,即NativeModule,如ToastModule,DialogModule,或是创建View的UIManagerModule。业务方可以通过实现NativeModule自定义模块,通过重写getName将模块名暴露给Js层,通过注解的方式将API暴露给Js层调用。
- 另一种是Js层暴露给Java层的API集合模块,即JavascriptModule,如DeviceEventEmitter,AppRegistry等。业务方可以通过继承JavaScriptModule接口自定义接口模块,申明与Js层相应的方法即可。
- 无论是NativeModule还是JavascriptModule,在Js层存在与之相互映射同名的Module,Js层通过require引用Module
- 模块注册表
-
- 各模块信息统一收集到模块注册表。同样,在RN中存在两种模块注册表,一是由集合所有Java层模块接口信息的NativeModuleRegistry,另一种是集合所有Js层模块接口信息的JavascriptModuleRegistry。在启动RN后,终端将注册表信息存入与前端互通的全局变量 __fbBatchedBridgeConfig 中,使得Js层与Java层存在同样的模块注册表
- 正如文章第一个时序图,从终端启动,入口是ReactRootView.startReactApplication,在runCreateReactContextOnNewThread方法中,创建ReactContext,这部分主要工作是:构造JavaScriptExecutor&JSBundleLoader后,创建NativeModules,JavaScriptModule及其对的注册表,负责Js与Java通信的高层接口CatalystInstance等。在创建完ReactContext后,通过CatalystInstance获取AppRegistry并调用其runApplication启动Js Application
- 源码
-
- 接下来进入正题,从源码来分析RN72的启动(为阅读方便,源码适当裁剪)
- 入口
-
- ReactRootView.startReactApplication
- ReactInstanceManager createReactContextInBackground
- ReactInstanceManager createReactContextInBackground,在ui主线程初始化触发ReactNative上下文创建过程(RN上下文创建是在一个新线程完成的)。mJSModuleName是与前端约定好所要启动的JS Application Name。mLauncahOptions是终端启动前端Application可选的传入的参数。
-
- createReactContextInBackground很有意思:
-
- 从函数命名的语义,该方法用于后台;从代码逻辑上看(会新建一个线程);从函数的标记上看,却为
@ThreadConfined("UI")(应该只在 UI 线程上执行) - 我这个安卓门外汉看的一愣一愣的
- createReactContextInBackground最终调用到runCreateReactContextOnNewThread创建新context。
- ps:在执行初始化前会销毁先前的上下文,保证只存在一个上下文。随后
- runCreateReactContextOnNewThread会调用createReactContext进一步创建ReactContext,重点是生成两个实例 : jsExecutor&jsBundleLoader。
-
- jsExecutor继承自JavaScriptExecutorFactory,
- 在jsExecutor创建过程中(JavaScriptExecutorFactory实例化)会依赖于已经加载的 SO 库来执行其功能,并且,在初始jsExecutor时会调用initialze去初始C++层ReactNative与JSC的通信框架等。
- jsBundleLoader缓存了JsBundle的信息,封装了上层加载JsBundle相关接口,CatalystInstance通过其间接调用ReactBridge去加载文件
- 随后,在创建完React Context后会调用setUpReactContext,进而通知DevSupportManager更新上下文,更新生命周期,将ReactRootView做为Root View传递给UIManagerModule,调用AppRegistry的runApplication去启动Js Application等
-
- 从函数命名的语义,该方法用于后台;从代码逻辑上看(会新建一个线程);从函数的标记上看,却为
- 模块