React Native之原理浅析

# 一、JavaScriptCore

讲 React Native 之前,了解 JavaScriptCore 会有帮助,也是必要的。React Native 的核心驱动力就来自于 JS Engine. 你写的所有 JS 和 JSX 代码都会被 JS Engine 来执行, 没有 JS Engine 的参与,你是无法享受 ReactJS 给原生应用开发带来的便利的。在 iOS 上,默认的就是 JavaScriptCore, iOS 7 之后的设备都支持. iOS 不允许用自己的 JS Engine. JavaScriptCore 来自于 WebKit, 所以,安卓上默认也是用 JavaScriptCore

你深入了解React Native的第一站应该是 JavaScriptCore

  • JavaScriptCoreiOS平台上给React Native提供的接口也仅限于那几个接口,你弄明白了JavaScriptCore那几个接口, React Native 剩下的魔法秘密都可以顺藤摸瓜来分析了。
  • 接下来要讲解的就是 Facebook 围绕这几个接口以及用一个 React 来颠覆整个 native 开发所做的精妙设计和封装

# 二、浏览器工作原理

  • 浏览器通过Dom Render来渲染所有的元素.
  • 浏览器有一整套的 UI 控件,样式和功能都是按照 html 标准实现的
  • 浏览器能读懂 html 和 css。
  • html 告诉浏览器绘制什么控件(html tag),css 告诉浏览器每个类型的控件(html tag)具体长什么样。
  • 浏览器的主要作用就是通过解析 html 来形成 dom 树,然后通过 css 来点缀和装饰树上的每一个节点

UI 的描述和呈现分离开了

  1. html 文本描述了页面应该有哪些功能,css 告诉浏览器该长什么样。
  2. 浏览器引擎通过解析 html 和 css,翻译成一些列的预定义 UI 控件,
  3. 然后 UI 控件去调用操作系统绘图指令去绘制图像展现给用户。
  4. Javascript 可有可无,主要用于 html 里面一些用户事件响应,DOM 操作、异步网络请求和一些简单的计算

在 react native 里面,1 和 2 是不变的,也是用 html 语言描述页面有哪些功能,然后 stylesheet 告诉浏览器引擎每个控件应该长什么样。并且和浏览器用的是同一个引擎

在步骤 3 里面 UI 控件不再是浏览器内置的控件,而是react native自己实现的一套 UI 控件(两套,android 一套,ios 一套),这个切换是在MessageQueque中进行的,并且还可以发现,他们 tag 也是不一样的

Javascript 在 react native 里面非常重要

  • 它负责管理 UI component 的生命周期,管理 Virtual DOM
  • 所有业务逻辑都是用 javascript 来实现或者衔接
  • 调用原生的代码来操纵原生组件。
  • Javascript 本身是无绘图能力的,都是通过给原生组件发指令来完成

# 三、React Native 架构

  • 绿色的是我们应用开发的部分。我们写的代码基本上都是在这一层
  • 蓝色代表公用的跨平台的代码和工具引擎,一般我们不会动蓝色部分的代码
  • 黄色代码平台相关的代码,做定制化的时候会添加修改代码。不跨平台,要针对平台写不同的代码。iOS 写 OC, android 写 java,web 写 js. 每个 bridge 都有对应的 js 文件,js 部分是可以共享的,写一份就可以了。如果你想做三端融合,你就得理解这一个东西。如果你要自己定制原生控件,你就得写 bridge 部分
  • 红色部分是系统平台的东西。红色上面有一个虚线,表示所有平台相关的东西都通过 bridge 隔离开来了
  • 大部分情况下我们只用写绿色的部分,少部分情况下会写黄色的部分。你如果对基础架构和开源感兴趣,你可以写蓝色部分,然后尝试给那些大的开源项目提交代码。红色部分是独立于 React Native 的

# 四、React Native、React 和 JavascriptCore 的关系

React Native 最重要的三个概念应该就是React NativeReactJavascriptCore

  • React 是一个纯 JS 库,所有的 React 代码和所有其它的 js 代码都需要 JS Engine 来解释执行。因为种种原因,浏览器里面的 JS 代码是不允许调用自定义的原生代码的,而 React 又是为浏览器 JS 开发的一套库,所以,比较容易理解的事实是 React 是一个纯 JS 库,它封装了一套 Virtual Dom 的概念,实现了数据驱动编程的模式,为复杂的 Web UI 实现了一种无状态管理的机制, 标准的 HTML/CSS 之外的事情,它无能为力。调用原生控件,驱动声卡显卡,读写磁盘文件,自定义网络库等等,这是 JS/React 无能为力的
  • 你可以简单理解为 React 是一个纯 JS 函数, 它接受特定格式的字符串数据,输出计算好的字符串数据
  • JS Engine 负责调用并解析运行这个函数
  • React Native呢? 它比较复杂。复杂在哪里?前面我们说了 React 是纯 JS 库,意味着 React 只能运行 JS 代码,通过 JS Engine 提供的接口(Html Tag)绘制 html 支持的那些元素,驱动有限的声卡显卡。简单点说, React 只能做浏览器允许它做的事情, 不能调用原生接口, 很多的事情也只能干瞪眼

React Native 它可不一样

  • 第一点,驱动关系不一样。前面我们说的是, JS Engine 来解析执行 React 脚本, 所以,React 由浏览器(最终还是 JS Engine)来驱动. 到了 React Native 这里,RN 的原生代码(Timer 和用户事件)驱动 JS Engine, 然后 JS Engine 解析执行 React 或者相关的 JS 代码,然后把计算好的结果返回给 Native code. 然后, Native code 根据 JS 计算出来的结果驱动设备上所有能驱动的硬件。重点,所有的硬件。也就是说,在 RN 这里,JS 代码已经摆脱 JS Engine(浏览器)的限制,可以调用所有原生接口啦
  • 第二点, 它利用 React 的 Virtual Dom 和数据驱动编程概念,简化了我们原生应用的开发, 同时,它不由浏览器去绘制,只计算出绘制指令,最终的绘制还是由原生控件去负责,保证了原生的用户体验

React Native 组件结构

驱动硬件的能力决定能一个软件能做多大的事情,有多大的主控性。研究过操作系统底层东西或者汇编的同学明白,我们大部分时候写的代码是受限的代码,很多特权指令我们是没法使用的,很多设备我们是不允许直接驱动的。我们现在的编程里面几乎已经没有人提中断了,没有中断,硬件的操作几乎会成为一场灾难.

在一定程度上,React Native 和 NodeJS 有异曲同工之妙。它们都是通过扩展 JavaScript Engine, 使它具备强大的本地资源和原生接口调用能力,然后结合 JavaScript 丰富的库和社区和及其稳定的跨平台能力,把 javascript 的魔力在浏览器之外的地方充分发挥出来

JavaScriptCore + ReactJS + Bridges 就成了 React Native

  • JavaScriptCore负责 JS 代码解释执行
  • ReactJS负责描述和管理VirtualDom,指挥原生组件进行绘制和更新,同时很多计算逻辑也在 js 里面进行。ReactJS 自身是不直接绘制 UI 的,UI 绘制是非常耗时的操作,原生组件最擅长这事情。
  • Bridges用来翻译 ReactJS 的绘制指令给原生组件进行绘制,同时把原生组件接收到的用户事件反馈给ReactJS
    要在不同的平台实现不同的效果就可以通过定制Bridges来实现

深入 Bridge 前面有提到, RN 厉害在于它能打通 JS 和 Native Code, 让 JS 能够调用丰富的原生接口,充分发挥硬件的能力, 实现非常复杂的效果,同时能保证效率和跨平台性。

打通 RN 任督二脉的关键组件就是Bridge. 在 RN 中如果没有 Bridge, JS 还是那个 JS,只能调用 JS Engine 提供的有限接口,绘制标准 html 提供的那些效果,那些摄像头,指纹,3D 加速,声卡, 视频播放定制等等,JS 都只能流流口水,原生的、平台相关的、设备相关的效果做不了, 除非对浏览器进行定制

  • Bridge 的作用就是给 RN 内嵌的 JS Engine 提供原生接口的扩展供 JS 调用。所有的本地存储、图片资源访问、图形图像绘制、3D 加速、网络访问、震动效果、NFC、原生控件绘制、地图、定位、通知等都是通过 Bridge 封装成 JS 接口以后注入 JS Engine 供 JS 调用。理论上,任何原生代码能实现的效果都可以通过 Bridge 封装成 JS 可以调用的组件和方法, 以 JS 模块的形式提供给 RN 使用。
  • 每一个支持 RN 的原生功能必须同时有一个原生模块和一个 JS 模块,JS 模块是原生模块的封装,方便 Javascript 调用其接口。Bridge 会负责管理原生模块和对应 JS 模块之间的沟通, 通过 Bridge, JS 代码能够驱动所有原生接口,实现各种原生酷炫的效果。
  • RN 中 JS 和 Native 分隔非常清晰,JS 不会直接引用 Native 层的对象实例,Native 也不会直接引用 JS 层的对象实例(所有 Native 和 JS 互掉都是通过 Bridge 层会几个最基础的方法衔接的)。
  • Bridge 原生代码负责管理原生模块并生成对应的 JS 模块信息供 JS 代码调用。每个功能 JS 层的封装主要是针对 ReactJS 做适配,让原生模块的功能能够更加容易被用 ReactJS 调用。MessageQueue.jsBridge在 JS 层的代理,所有 JS2N 和 N2JS 的调用都会经过MessageQueue.js来转发。JS 和 Native 之间不存在任何指针传递,所有参数都是字符串传递。所有的 instance 都会被在 JS 和 Native 两边分别编号,然后做一个映射,然后那个数字/字符串编号会做为一个查找依据来定位跨界对象。

# 五、Bridge 各模块简介

# 5.1 RCTRootView

  • RCTRootViewReact Native加载的地方,是万物之源。从这里开始,我们有了 JS Engine, JS 代码被加载进来,对应的原生模块也被加载进来,然后 js loop 开始运行。 js loop 的驱动来源是 Timer 和 Event Loop(用户事件). js loop 跑起来以后应用就可以持续不停地跑下去了。
  • 如果你要通过调试来理解 RN 底层原理,你也应该是从 RCTRootView 着手,顺藤摸瓜。
  • 每个项目的AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:里面都可以看到 RCTRootView 的初始化代码,RCTRootView 初始化完成以后,整个 React Native 运行环境就已经初始化好了,JS 代码也加载完毕,所有 React 的绘制都会有这个 RCTRootView 来管理。

RCTRootView 做的事情如下

  • 创建并且持有RCTBridge
  • 加载JS Bundle并且初始化 JS 运行环境.
  • 初始化 JS 运行环境的时候在 App 里面显示loadingView, 注意不是屏幕顶部的那个下拉悬浮进度提示条. RN 第一次加载之后每次启动非常快,很少能意识到这个加载过程了。loadingView 默认情况下为空, 也就是默认是没有效果的。loadingView 可以被自定义,直接覆盖 RCTRootView.loadingView 就可以了.开发模式下 RN app 第一次启动因为需要完整打包整个 js 所以可以很明显看到加载的过程,加载第一次以后就看不到很明显的加载过程了,可以执行下面的命令来触发重新打包整个 js 来观察loadingView的效果 watchman watch-del-all && rm -rf node_modules/ && yarn install && yarn start – –reset-cache, 然后杀掉app重启你就会看到一个很明显的进度提示.
  • JS运行环境准备好以后把加载视图用RCTRootContentView替换加载视图
  • 有准备工作就绪以后调用AppRegistry.runApplication正式启动 RN JS 代码,从Root Component()开始 UI 绘制

一个 App 可以有多个RCTRootView, 初始化的时候需要手动传输Bridge做为参数,全局可以有多个RCTRootView, 但是只能有一个Bridge

如果你做过React Native和原生代码混编,你会发现混编就是把AppDelegate里面那段初始化RCTRootView的代码移动到需要混编的地方,然后把RCTRootView做为一个普通的subview来加载到原生的view里面去,非常简单。不过这地方也要注意处理好单 Bridge 实例的问题,同时,混编里面要注意RCTRootView如果销毁过早可能会引发 JS 回调奔溃的问题

# 5.2 RCTRootContentView

  • RCTRootContentView reactTag在默认情况下为 1. 在Xcode view Hierarchy debugger 下可以看到,最顶层为RCTRootView, 里面嵌套的是RCTRootContentView, 从RCTRootContentView开始,每个 View 都有一个reactTag
  • RCTRootView继承自 UIView, RCTRootView 主要负责初始化JS Environment和 React 代码,然后管理整个运行环境的生命周期。 RCTRootContentView继承自RCTView, RCTView继承自 UIView, RCTView 封装了 React Component Node 更新和渲染的逻辑, RCTRootContentView会管理所有 react ui components. RCTRootContentView同时负责处理所有 touch 事件

# 5.3 RCTBridge

这是一个加载和初始化专用类,用于前期 JS 的初始化和原生代码的加载

  • 负责加载各个 Bridge 模块供 JS 调用
  • 找到并注册所有实现了RCTBridgeModule protocol的类, 供 JS 后期使用.
  • 创建和持有 RCTBatchedBridge

# 5.4 RCTBatchedBridge

如果 RCTBridge 是总裁, 那么 RCTBatchedBridge 就是副总裁。前者负责发号施令,后者负责实施落地

  • 负责 Native 和 JS 之间的相互调用(消息通信)
  • 持有JSExecutor
  • 实例化所有在 RCTBridge 里面注册了的native node_modules
  • 创建 JS 运行环境, 注入native hooksmodules, 执行 JS bundle script
  • 管理 JS run loop, 批量把所有 JS 到 native 的调用翻译成native invocations
  • 批量管理原生代码到 JS 的调用,把这些调用翻译成 JS 消息发送给JS executor

# 5.5 RCTJavaScriptLoader

这是实现远程代码加载的核心。热更新,开发环境代码加载,静态jsbundle加载都离不开这个工具。

  • 从指定的地方(bundle, http server)加载 script bundle
  • 把加载完成的脚本用string的形式返回
  • 处理所有获取代码、打包代码时遇到的错误

# 5.6 RCTContextExecutor

  • 封装了基础的 JS 和原生代码互掉和管理逻辑,是 JS 引擎切换的基础。通过不同的 RCTCOntextExecutor 来适配不同的 JS Engine,让我们的 React JS 可以在 iOS、Android、chrome 甚至是自定义的 js engine 里面执行。这也是为何我们能在 chrome 里面直接调试 js 代码的原因
  • 管理和执行所有 N2J 调用

# 5.7 RCTModuleData

  • 加载和管理所有和 JS 有交互的原生代码。把需要和 JS 交互的代码按照一定的规则自动封装成 JS 模块
  • 收集所有桥接模块的信息,供注入到 JS 运行环境

# 5.8 RCTModuleMethod

记录所有原生代码的导出函数地址(JS 里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS 调用原生函数的时候会通过 message 的形式调用过来

  • 记录所有的原生代码的函数地址,并且生成对应的字符串映射到该地址
  • 记录所有的 block 的地址并且映射到唯一的一个 id
  • 翻译所有J2N call,然后执行对应的 native 方法
  • 如果是原生方法的调用则直接通过方法名调用,MessageQueue 会帮忙把 Method 翻译成 MethodID, 然后转发消息给原生代码,传递函数签名和参数给原生 MessageQueue, 最终给 RCTModuleMethod 解析调用最终的方法
  • 如果 JS 调用的是一个回调 block,MessageQueue 会把回调对象转化成一个一次性的 block id, 然后传递给 RCTModuleMethod, 最终由 RCTModuleMethod 解析调用。基本上和方法调用一样,只不过生命周期会不一样,block 是动态生成的,要及时销毁,要不然会导致内存泄漏

实际上是不存在原生 MessageQueue 对象模块的,JS 的 MessageQueue 对应到原生层就是 RCTModuleData & RCTModuleMethod 的组合, MessageQueue 的到原生层的调用先经过 RCTModuleData 和 RCTModuleMethod 翻译成原生代码调用,然后执行

# 5.9 MessageQueue

  • 这是核心中的核心。整个 react native 对浏览器内核是未做任何定制的,完全依赖浏览器内核的标准接口在运作。它怎么实现 UI 的完全定制的呢?它实际上未使用浏览器内核的任何 UI 绘制功能,注意是未使用 UI 绘制功能。它利用 javascript 引擎强大的 DOM 操作管理能力来管理所有 UI 节点,每次刷新前把所有节点信息更新完毕以后再给 yoga 做排版,然后再调用原生组件来绘制。javascript 是整个系统的核心语言。
  • 我们可以把浏览器看成一个盒子,javascript 引擎是盒子里面的总管,DOM 是 javascript 引擎内置的,javascript 和 javascript 引擎也是无缝链接的。react native 是怎么跳出这个盒子去调用外部原生组件来绘制 UI 的呢?秘密就在 MessageQueue。
  • javascript 引擎对原生代码的调用都是通过一套固定的接口来实现,这套接口的主要作用就是记录原生接口的地址和对应的 javascript 的函数名称,然后在 javascript 调用该函数的时候把调用转发给原生接口

# 六、React Native 初始化

React Native的初始化从RootView开始,默认在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 里面会有RootViewd的初始化逻辑,调试的时候可以从这里入手

React Native 的初始化分为几个步骤

  • 原生代码加载
  • JS Engine初始化(生成一个空的 JS 引擎)
  • JS 基础设施初始化. 主要是 require 等基本模块的加载并替换 JS 默认的实现。自定义require, Warning window, Alert window, fetch等都是在这里进行的。基础设施初始化好以后就可以开始加载 js 代码了
  • 遍历加载所有要导出给 JS 用的原生模块和方法, 生成对应的 JS 模块信息,打包成 json 的格式给 JS Engine, 准确地说是给 MessageQueue.

这里需要提一下的是

这里的导出是没有对象的,只有方法和模块。JS 不是一个标准的面向对象语言,刚从 Java 转 JavaScript 的同学都会在面向对象这个概念上栽跟头,这里特别提醒一下

# 6.1 原生代码初始化

这里讨论的主要是 RN 相关的原生代码和用户自定义的 RN 模块的原生代码的加载和初始化。原生代码初始化主要分两步

  • 静态加载。iOS 没有动态加载原生代码的接口,所有的代码都在编译的初期就已经编译为静态代码并且链接好,程序启动的时候所有的原生代码都会加载好。这是原生代码的静态加载,iOS 里面没有动态加载原生代码的概念,这也是为何没有静态代码热更新的原因
  • RN 模块解析和注入 JS。这是加载的第二步。在 RootView 初始化的时候会遍历所有被标记为 RCTModule 的原生模块,生成一个 json 格式的模块信息,里面包含模块名称和方法名称,然后注入到 JS Engine, 由 MessageQueue 记录下来。原生代码在生成 json 模块信息的时候同时会在原生代码这边维护一个名称字典,用来把模块和方法的名称映射到原生代码的地址上去,用于 JS 调用原生代码的翻译

# 6.2 Javascript 环境初始化

  • RN 的初始化是从 RCRootView 开始的,所有的绘制都会在这个 RootView 里面进行(Alert 除外)
  • RootView 做的第一件事情就是初始化一个空的 JS Engine。 这个空的 JS Engine 里面包含一些最基础的模块和方法(fetch, require, alert 等), 没有 UI 绘制模块。 RN 的工作就是替换这些基础的模块和方法,然后把 RN 的 UI 绘制模块加载并注入到 JS Engine.

JS Engine 不直接管理 UI 的绘制

  • 所有的绘制由原生控制的 UI 事件和 Timer 触发
  • 影响界面刷新的事件发生以后一部分直接由原生控件消化掉,直接更新原生控件。剩下的部分会通过Bridge派发给 MessageQueue,然后在 JS 层进行业务逻辑的计算,再由React来进行 Virtual Dom 的管理和更新。Virtual Dom再通过 MessageQueue 发送重绘指令给对应的原生组件进行 UI 更新

# 6.3 NativeModules 加载

  • 在 OC 里面,所有 NativeModules 要加载进 JS Engine 都必须遵循一定的协议(protocol)。
  • 模块(OC 里面的类)需要声明为, 然后在类里面还必须调用宏 RCT_EXPORT_MODULE() 用来定义一个接口告诉 JS 当前模块叫什么名字。这个宏可以接受一个可选的参数,指定模块名,不指定的情况下就取类名。
  • 对应的 JS 模块在初始化的时候会调用原生类的[xxx new]方法- 模块声明为<RCTBridgeModule>后只是告诉 Native Modules 这有一个原生模块,是一个空的模块。要导出任何方法给 JS 使用都必须手动用宏 RCT_EXPORT_METHOD 来导出方法给 JS 用.
  • 所有的原生模块都会注册到NativeModules这一个 JS 模块下面去,你如果想要让自己的模块成为一个顶级模块就必须再写一个 JS 文件封装一遍 NativeModules 里面的方法。
  • 你如果想自己的方法导出就默认成为顶级方法,那么你需要一个手动去调用 JSC 的接口,这个在前面章节有讲解。 不建议这样做,因为这样你会失去跨 JS 引擎的便利性。
  • 你可以导出常量到 JS 里面去, 模块初始化的时候会坚持用户是否有实现constantsToExport 方法, 接受一个常量词典

- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };// JS 里面可以直接调用 ModuleName.firstDayOfTheWeek 获取这个常量
}

  • 常量只会在初始化的时候调用一次,动态修改该方法的返回值无效
  • 所有标记为 RCT_EXPORT_MODULE 的模块都会在程序启动的时候自动注册好这些模块,主要是记录模块名和方法名。只是注册,不一定会初始化。
  • Native Modules导出宏具体使用方法见官方文档Native Modules

# 6.4 三个线程

React Native 有三个重要的线程:

  • Shadow queue. 布局引擎(yoga)计算布局用的
  • Main thread. 主线程。就是操作系统的 UI 线程。无论是 iOS 还是 android,一个进程都只有一个 UI 线程,我们常说的主线程. React Native 所有 UI 绘制也是由同一个 UI 线程来维护
  • Javascript thread. javascript线程。 大家都知道 javascript 是单线程模型,event 驱动的异步模型。React Native 用了 JS 引擎,所以也必需有一个独立的 js 线程. 所有 JS 和原生代码的交互都发生在这个线程里。死锁,异常也最容易发生在这个线程

可以看到 Shadow queue 是 queue 而不是 thread, 在 iOS 里面 queue 是 thread 之上的一层抽象,GCD 里面的一个概念,创建 queue 的时候可以指定是并行的还是串行的。也就是说,一个 queue 可能对应多个 thread

# 七、内部机制

内部机制

JS 用时序

# 八、总结

# 8.1 React Native 框架分析

# 8.2 层次架构

  • Java 层:该层主要提供了 Android 的 UI 渲染器UIManager(将 JavaScript 映射成Android Widget)以及一些其他的功能组件(例如:Fresco、Okhttp)等,在 java 层均封装为 Module,java 层核心 jar 包是 react-native.jar,封装了众多上层的 interface,如 Module,Registry,bridge 等
  • C++层:主要处理 Java 与 JavaScript 的通信以及执行 JavaScript 代码工作,该层封装了 JavaScriptCore,执行对 js 的解析。基于JavaScriptCoreWeb开发者可以尽情使用 ES6 的新特性,如 class、箭头操作符等,而且 React Native 运行在JavaScriptCore中的,完全不存在浏览器兼容的情况。Bridge 桥接了 java , js 通信的核心接口。JSLoader 主要是将来自 assets 目录的或本地 file 加载 javascriptCore,再通过JSCExectutor解析 js 文件
  • Js 层:该层提供了各种供开发者使用的组件以及一些工具库。
    Component:Js 层通 js/jsx 编写的Virtual Dom来构建Component或 Module,Virtual DOM 是 DOM 在内存中的一种轻量级表达方式,可以通过不同的渲染引擎生成不同平台下的 UI。component 的使用在 React 里极为重要, 因为 component 的存在让计算 DOM diff 更高效。
    ReactReconciler : 用于管理顶层组件或子组件的挂载、卸载、重绘

注:JSCore,即 JavaScriptCore,JS 解析的核心部分,IOS 使用的是内置的JavaScriptCore,Androis 上使用的是 https://webkit.org (opens new window) 家的 jsc.so。

Java 层核心类及原理,如下所示

ReactContext

  • ReactContext 继承于 ContextWrapper,是 ReactNative 应用的上下文,通过 getContext()去获得,通过它可以访问 ReactNative 核心类的实现。

ReactInstanceManager

  • ReactInstanceManager是 ReactNative 应用总的管理类,创建ReactContextCatalystInstance等类,解析ReactPackage生成映射表,并且配合ReactRootView管理 View 的创建与生命周期等功能。

ReactRootView

  • 为启动入口核心类,负责监听及分发事件并重新渲染元素,App 启动后,其将作为 App 的root view

CatalystInstance

  • CatalystInstanceReactNative应用 Java 层、C++层、JS 层通信总管理类,总管 Java 层、JS 层核心Module映射表与回调,三端通信的入口与桥梁。

JavaScriptModule

  • JavaScriptModuleJS Module,负责 JS 到 Java 的映射调用格式声明,由CatalystInstance统一管理。

NativeModule

  • NativeModulejava Module,负责 Java 到 Js 的映射调用格式声明,由CatalystInstance统一管理。

JavascriptModuleRegistry

  • JS Module 映射表,负责将所有 JavaScriptModule 注册到 CatalystInstance,通过 Java 动态代理调用到 Js。

NativeModuleRegistry

  • 是 Java Module 映射表,即暴露给 Js 的 API 集合。

CoreModulePackage

  • 定义核心框架模块,创建NativeModules&JsModules

# 8.3 启动过程的解析

  1. ReactInstanceManager 创建时会配置应用所需的 java 模块与 js 模块,通过 ReactRootView 的 startReactApplication 启动 APP。
  2. 在创建 ReactInstanceManager 同时会创建用于加载 JsBundle 的 JSBundlerLoader,并传递给 CatalystInstance。
  3. CatalystInstance 会创建 Java 模块注册表及 Javascript 模块注册表,并遍历实例化模块。
  4. CatalystInstance 通过 JSBundlerLoader 向 Node Server 请求 Js Bundle,并传递给 JSCJavaScriptExectutor,最后传递给 javascriptCore,再通过 ReactBridge 通知 ReactRootView 完成渲染

# 8.4 Js 与 Java 通信机制

Java 与 Js 之间的调用,是以两边存在两边存在同一份模块配置表,最终均是将调用转化为{moduleID,methodID,callbackID,args},处理端在模块配置表里查找注册的模块与方法并调用。

Java 调用 Js

Java 通过注册表调用到 CatalystInstance 实例,透过 ReactBridge 的 jni,调用到 Onload.cpp 中的 callFunction,最后通过 javascriptCore,调用 BatchedBridge.js,根据参数{moduleID,methodID}require 相应 Js 模块执行。流程如下图:

Js 调用 Java

如果消息队列中有等待 Java 处理的逻辑,而且 Java 超过 5ms 都没有来取走,那么 JavaScript 就会主动调用 Java 的方法,在需要调用调 Java 模块方法时,会把参数{moduleID,methodID}等数据存在 MessageQueue 中,等待 Java 的事件触发,把 MessageQueue 中的{moduleID,methodID}返回给 Java,再根据模块注册表找到相应模块处理。流程如下图:

# 九、更多参考