5

React Native重大架构升级即将发布

 2 years ago
source link: https://segmentfault.com/a/1190000040629524
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

React Native重大架构升级即将发布

7 月 14 日,React Native 核心团队的 Joshua Gross 在 Twitter 说,RN 的新架构已经在 Facebook 内部落地了,并且99%的代码已经开源。其实,早在 2018 年 6 月,Facebook 官方就宣布了大规模重构 React Native 的计划及重构路线图,目的是为了让 React Native 更加轻量化、更适应混合开发,接近甚至达到原生的体验。

这次的架构升级对于 React Native 意义重大,按照官方的说法,升级后的RN性能将得到大幅提升,主要是解决诟病已久的性能问题,下图是React Native的一个版本发布说明。

为了让 RN 更轻量化、更适应混合开发,接近甚至达到原生的体验,React Native的优化措施包括以下几个方面:

  • 改变线程模型,UI 更新不再同时需要在三个不同的线程上触发执行,而是可以在任意线程上同步调用 JavaScript 进行优先更新,同时将低优先级工作推出主线程,以便保持对 UI 的响应。
  • 引入异步渲染能力,允许多个渲染并简化异步数据处理。
  • 简化了 JSBridge的渲染逻辑,优化了底层渲染架构,让它更快更轻量。

二、新老架构对比

对于有过RN开发经验的都知道,原有RN架构 JS 层与 Native 的通讯过多的依赖 Bridge,而且是异步通讯,这就造成一些通讯频率较高的交互和设计就很难实现,同时也影响页面的渲染。而新架构也正是从这一点出发,对 Bridge 这层做了大量的改造,使得 UI 和 API 的调用,从原有异步方式调整到可以同步或者异步与 Native 通讯,解决了频繁通讯的瓶颈问题。

同时,新架构使用JSI(全称是 JavaScript Interface)代替原来的 Bridge, JS层直接调用 C++层进而调用 Java/OC 的方式,实现 JS 和 Java/OC 之间的相互操作,大大提高了通讯的效率。得益于JSI的全新架构,JavaScript 可以直接调用 Native 模块的方法。

三、旧架构

在介绍新架构之前,我们先看一下 RN框架目前的架构,以及它的一个缺点,以及为什么 Facebook 要重构整个框架。目前,RN使用的架构主要包含Native、JavaScript与Bridge三个部分。Native 管理 UI 更新及交互,JavaScript 调用 Native 能力实现业务功能,Bridge 在二者之间传递消息,整个架构如下图。
在这里插入图片描述
可以看到,React Native的架构还是很清晰的。最上层提供类 React 支持,运行在JavaScriptCore提供的 JavaScript 运行时环境中,Bridge 层将 JavaScript 与 Native 世界连接起来。然后,Bridge分为了三部分,其中Shadow Tree 用来定义 UI 效果及交互功能,Native Modules 提供 Native 功能(比如相册、蓝牙),而他们之间的相互通信 使用的是JSON 异步消息。

3.1 Bridge

在现在的架构中,Bridge 层是 React Native 技术的关键,它具有以下一些特点:

  • 异步(asynchronous):不依赖于同步通信。
  • 可序列化(serializable):保证一切 UI 操作都能序列化成 JSON 并转换回来。
  • 批处理(batched):对 Native 调用进行排队,批量处理。

3.2 线程模型

在旧架构中,React Native一共有 3 个线程,分别是UI Thread、Shadow Thread和JS Thread。

  • UI Thread:Android/iOS(或其它平台)应用中的主线程。
  • Shadow Thread:进行布局计算和构造 UI 界面的线程。
  • JS Thread:React 等 JavaScript 代码都在这个线程执行。

它们之前的关系如下图:
在这里插入图片描述

3.3 启动流程

对于RN应用来说,App 启动后首先需要初始化 React Native 运行时环境(即 Bridge),Bridge 准备好之后接着才能运行JS代码,然后执行Native 渲染。完整的启动过程如下:
在这里插入图片描述
其中,初始化 Bridge涉及到如下过程:
在这里插入图片描述
可以看到,初始化 Bridge主要分为4个步骤:

  • 加载 JavaScript 代码:开发模式下从网络下载,生产环境从设备存储中读取
  • 初始化 Native Modules:根据 Native Module 注册信息,加载并实例化所有 Native Module
  • 注入 Native Module 信息:取 Native Module 注册信息,作为全局变量注入到 JS Context 中
  • 初始化 JavaScript 引擎:即 JavaScriptCore

3.4 渲染流程

在这里插入图片描述
前面说过,React Native一共有 3 个线程,分别是UI Thread、Shadow Thread和JS Thread。
在渲染流程中,JS 线程将视图信息(结构、样式、属性等)传递给 Shadow 线程,创建出用于布局计算的 Shadow Tree,Shadow 线程计算好布局之后,再将完整的视图信息(包括宽高、位置等)传递给主线程,主线程据此创建视图。

对于需要响应的事件来说,则先由主线程将相关信息打包成事件消息传递到 Shadow 线程,再根据 Shadow Tree 建立的映射关系生成相应元素的指定事件,最后将事件传递到 JS 线程,执行对应的 JS 回调函数。
在这里插入图片描述
完整的渲染流程如下图:

在这里插入图片描述
通过上面的分析,不难发现现在的架构是强依赖 nativemodule,也就是大家通常说的 bridge,对于简单的 Native API 调用来说性能还能接受,而对于 UI 来说,每次的操作都是需要通过 bridge 的,包括高度计算、更新等,且 bridge 限制了调用频率、只允许异步操作,导致一些前端的更新很难及时反应到 UI 上,特别是类似于滑动、动画,更新频率较高的操作,所以经常能看到白屏或者卡顿。

四、新架构

现在的架构, JS 层与 Native 的通讯都太依赖Bridge,导致一些通讯频率较高的交互和设计就很难实现,同时也影响了渲染性能。基于上面的问题,在新的设计上,React Native 提出了几个新的概念和设计:JSI、Fabric和TuborModule。

  • JSI(JavaScript interface):本次架构重构的核心重点,也正是JSI的缘故,原有重度依赖的 Native Bridge 架构得到解耦,JS 层与 Native 的通讯成本大大降低。
  • Fabric:依赖 JSI 的设计,将旧架构下的 shadow tree 层移到 C++ 层,这样可以通过 JSI实现前端组件对 UI 组件的一对一控制,摆脱了旧架构下对于 UI 的异步、批量操作,降低了通讯成本。
  • TuborModule:新的原生 API 架构,替换了原有的 Java module 架构,数据结构上除了支持基础类型外,开始支持 JSI 对象,让前端和客户端的 API 形成一对一的调用。

下面是React Native 的新的架构示意图:
在这里插入图片描述
可以看到,在新的架构方案上,Bridge层被新的JSI代替,不同于之前直接将 JavaScript 代码输入给 JSC,新的架构中引入了一层 JSI(JavaScript Interface)。作为 JSC 之上的抽象,JSI用来屏蔽 JavaScript 引擎的差异,允许换用不同的 JavaScript 引擎(比如Hermes)。

新的 JSI 层又包含了 Fabric 和 TurboModules 两部分。其中,Fabric负责管理 UI,TurboModules负责与 Native 交互。Fabric 以更现代化的方式去实现 React Native 的渲染层,简化之前渲染流程中复杂跨线程交互流程(React -> Native -> Shadow Tree -> Native UI)。而TurboModules也支持按需加载 Native 模块,从而缩短了RN初始化Native 模块带来的性能开销。

4.1 JSI

在这里插入图片描述
前面说过,为了升级RN的架构,RN提出了全新的JSI的概念,有了 JSI 之后,JavaScript 可以直接持有 C++对象的引用,并调用其方法。JSI 在 0.60 后的版本就已经开始支持,它是 Facebook 在 JS 引擎上设计的一个适配架构,它允许开发者向 JavaScript 运行时注册方法的 JavaScript 接口,而这些注册方法完全可以用 C++ 进行编写。除此之外,JSI还带来了如下的一些特性:

  • 标准化的 JS 引擎接口,React Native 可以替换 v8、Hermes 等引擎。
  • 优化升级 JS 和原生 java 或者 Objc 的通讯,但是不同于JSBridge采用的是内存共享、代理类的方式,为了实现和 Native 端直接通讯,JSI提供了一层 C++ 层实现的 JSI::HostObject,该数据结构支持 propName, 同时支持从 JS 传参。
  • 原有 JS 与 Native 的数据通讯采用 JSON 和基础类型数据,但有了 JSI 后,数据类型更丰富,支持 JSI Object。

所以 ,在新架构下API 调用流程:JS->JSI->C++->JNI->JAVA,每个 API 更加独立化,不再需要全部依赖 Native module。但这也带来了另外一个问题,就是开发者在设计一个 API需要封装 JS、C++、JNI、Java 等一套接口,对开发者的要求还是比较高的。不过,好在Facebook提供了一个 codegen 模块,可以帮助开发者完成基础代码和环境的搭建。

关于封装jsi的过程,可以参考开发JSI Module

4.2 Fabric

Fabric 是RN新架构的 UI 框架,和原有的UImanager 框架的作用类似,不过UImanager的渲染性能与原生端组件和动画的渲染性能还是有很大的差距的。举个比较常见的问题,Flatlist 快速滑动的状态下,会存在很长的白屏时间,交互比较强的动画、手势很难支持,因此RN采用了全新的Fabric框架。

简单来说,JS 层新设计了 FabricUIManager,目的是支持 Fabric render 完成组件的渲染与更新。由于采用了 JSI 的设计,FabricUIManager可以和 cpp 层直接进行通讯,对应 C++ 层 UIManagerBinding,其实每个操作和 API 调用都有对应创建了不同的 JSI,从这里就彻底解除了原有的全部依赖 UIManager 单个 Native bridge 的问题,同时组件大小的 measure 也摆脱了对 Java、bridge 的依赖,直接在 C++ 层 shadow 完成,提升渲染效率。

有了 JSI 后,以前批量依赖 bridge 的 UI 操作,都可以同步的执行到 c++ 层,而在 c++ 层,新架构完成了一个 shadow 层的搭建,而旧架构是在 java 层实现,所以从这方面来说,渲染的性能也得到了大幅的提升。

在这里插入图片描述

4.3 TurboModule

在之前的架构中,Native Modules(无论是否需要用到)都要在应用启动时进行初始化,因为 Native 不知道 JavaScript 将会调用哪些功能模块。而新的TurboModules 允许按需加载 Native 模块,并在模块初始化之后直接持有其引用,不再依靠消息通信来调用模块功能。因此,应用的启动时间也会有所提升。并且,0.64 版本已经支持 TurboModule的使用。

总的来说,TurboModule的设计就是为了方便 JS 可以直接调用到 c++ 的方法。

4.4 CodeGen

新架构 UI 增加了 C++ 层的 shadow、component 层,而且大部分组件都是基于 JSI,因而开发 UI 组件和 API 的流程更复杂了,要求开发者具有 c++、JNI 的编程能力,为了方便开发者快速开发 Facebook 也提供了 codegen 工具,帮助生成一些自动化的代码。

CodeGen工具参考,同时,因 codegen 目前还没有正式 release,关于如何使用的文档几乎没有,还得等开源后才会知道。

另外,JSI、Turbormodule 已经在最新的版本上已经可以体验,而且开发者社区也用 JSI 开发了大量的 API 组件,例如:

从最新的更新情况来看,RN的新架构离发布似乎已经进入倒计时,作为RN的忠实粉丝,也一直希望RN能够尽快的发布1.0 版本。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK