RN 的新架构虽然从 0.68 开始实验,但是其实 IOS 中的 JSI 在 0.58 就开始投入使用了,所以我们选择用 0.57 作为本章节讲解的 RN 版本
想要讲清楚任何一个框架的架构绝对不是一个非常容易的事情,特别是 RN 这种跨了多种语言的框架,所以在初版架构部份,我们会分成以下几个部分来进行讲解:
- RN 的线程模型
- RN 的通信机制
- RN 的 UI 布局与绘制
- RN 的触摸事件处理机制
- RN 的运行时异常与异常捕获处理
- RN 应用的启动流程
同时,我们在后续讨论中都会围绕着架构图来讲解,架构如下图所示:
RN 的线程模型
在上文中,我们聊了为什么 RN 要为 JS 独立出一个线程,但实际上,RN 在默认情况下至少会有 4 个线程,让我们来一一了解他们分别有什么作用:
Main thread
主线程,又被称之为 UI 线程,当用户开启一个 IOS 或 Android 应用时,它会是第一个被启动的线程
它负责处理 UI 的渲染以及用户的输入事件,所以如果它被阻塞了,会导致应用卡顿甚至无响应
JS thread
JS 线程,用于在 IOS/Android 中运行 JS 代码
RN 团队最终选用了 IOS 系统自带的 JSCore 来执行 JS 代码(个人猜测是因为 IOS 禁用第三方 JS 引擎所做的权衡)
这里稍微展开一点,在 rn 中 js 引擎的选择有几种情况:
- IOS:使用系统自带的 JSCore 框架,只能用 Apple 公开的 C API,并且由于 JSCore 是跟 IOS 系统绑定的,所以 RN 不能决定 JSCore 的版本
- Android:RN 团队把 android-jsc 打包到了 app 中,除了公开的 C API 之外, android-jsc 还对外暴露了一些内部 API,这个我们后面会稍微聊到
- remote debug:由于当时的历史原因,jsc 无法提供一些开发调试工具,所以 RN 团队给调试代码开辟了一条用 chrome v8 运行 js 然后通过 chrome devtool 调试代码的路径,在这种情况下 v8 会用 web socket 跟 RN APP 通信
与 React 类似,RN 也需要一个打包器来解析 jsx 并将其打包为 JS 代码,所以 RN 会先将代码交给 Metro 打包,打包的结果才会被送给 JSCore 执行
Shadow thread
Shadow thread 又称 UI manager,主要负责管理原生的 UI 元素
在我们聊 Shadow thread 干了什么之前,我们先来看看为什么需要 Shadow thread
在传统原生 App 的开发中,常见的 UI 更新一般包含有四个步骤:事件响应、状态更新、布局、绘制,而这四个步骤都是在主线程中以同步的方式进行的
在 RN 中,由于 JS 与主线程不在同一个线程中,如果我们依然让主线程来承担元素 layout 的工作,那么每次主线程 UI 更新都需要等待 JS 线程处理完状态更新后,再将变更的部份通过 Bridge(这个我们后面会聊,先当他是一个线程间通信手段) 发给主线程去重新布局及绘制,而且在这个过程中,主线程必须等待 JS 线程的结果,这会导致主线程频繁的等待
此外,React 可能会在一次渲染中提交多次小的更新,这个操作会触发主线程多次的布局操作,进而拖垮整个应用的性能
UI manager 就是用来解决上述性能问题的,而他的解决思路很简单:将布局的工作外包给另一个线程
有了 UI manager 后,我们可以将管理 UI 元素与计算布局的任务交给他,主线程只需要在接收到布局结果后直接应用就好
这样一来,我们就能够在最小化主线程布局计算开销了
此外,由于 UI manager 能够控制何时提交布局结果,他可以将多个 React 的更新打包成一个操作集合,并在每帧绘制前提交给主线程
要做到上述两点(计算布局、打包操作)UI manager 需要做到两件事情:
- 在 Shadow thread 使用原生语言(java/object-c)维护一个 Shadow tree
- 定制一个布局引擎,用来计算元素的布局信息
关于这部分的内容,本文的 UI 布局与绘制 章节会详细说明
Native module thread
由于 RN 本质上还是运行在原生平台之上的框架,所以在某些特定时刻,它会需要调用原生的 API 来实现特定功能
对同一类 API 的调用可以被我们抽象成一个个的模块(Module),比如 Platform 可以获取系统当前的信息、Clipboard 可以读写系统的剪贴板等等
由于这些 module 是基于 Native 语言与 API 实现的,所以 RN 将其称之为 Native module
与此同时,由于 Native module 对 JS 侧暴露了原生语言(Java/Objective-C/C++)的类实例,他们也是 JS 调用原生代码的唯一入口(甚至包括我们上文所述的 UIManager 也是一个 Native module)
除了使用系统自带以及社区提供的 Native module 之外,我们还可以自己编写符合自身需求的 Native module 以满足调用系统 API、复杂计算、网络请求等等需求,这无疑大大增加了 RN 的扩展性
值得一提的是,Native module thread 在大多数时候并不是指的是一个线程,更像是对一类线程的统称,具体 Native module thread 的线程数量取决于系统,以及具体的 Native module
关于 Native module 如何与 JS 进行通讯的,我们会在 RN 的通信机制 详细说明
以上,我们聊完了 RN 的线程模型以及每个线程的职责,下一篇文章我们来聊聊 RN 线程的通信机制,也就是 Bridge 是如何实现的