React新架构Fabric桥接介绍

December 20, 2019

英文原文地址:https://medium.com/swlh/react-native-a-bridge-to-project-fabric-part-1-5af6a53c0d83

翻译中有部分意译。

最近几年,React Native已经成为最流行的移动端App开发框架之一。解决了很多开发者经常思考的问题:应该使用原生开发客户端app,还是使用web开发Hybird app。

使用RN开发需要web方面的知识和React经验,因为它基本是基于React框架的。需要比直接使用Cordova这样库付更难一点,但是比写Swift和Kotlin 这两个原生客户端的代码要轻松很多。RN可以通过写纯js代码获得和原生类似的性能体验。

第一部分:RN的Bridge(桥接)是什么黑魔法?

在开始之前,我们先介绍一下,相比于其他方案,React Native的定位是什么。

React-Fabric介绍

在上图中,选取了一些常见的App开发方式。

从左到右,可以分成3个阵营:

  1. 原生Native。这意味着你想去使用原生语言开发App,比如ios的Swift或Android的Kotlin。肯定会获得最好的性能,并且可以充分利用设备硬件和原生API。但是,这需要学习两种不同的编程语言,维护两套代码,并且可能有双倍的bug,甚至需要有两个不同的开发团队。

  2. Hybrid。如果你是web开发者或者拥有一个已经熟悉JS、HTML、CSS和某些前端库的Web团队,你可以选择这种方案,并使用Cordova/Ionic 通过一些步骤使你的网页变成移动App。这样我们只需要学习一个技术栈,但是,在性能,硬件和API的使用上会有限制。

  3. 类原生(其实也算是一种Hybrid)。React Native就属于这类,理论上开发者只需要懂得Web开发知识就可以了。但是学习曲线会比Hybrid要高一些。开发者需要学习怎么使用React Native的库。在某些场景下,可能还需要使用XCode或Android Studio打开项目。但是写的代码可以适用于iOS和Android两个平台,开发上的限制也会比Hybrid要小一些。性能表现会更像原生开发,而且可以更容易地使用一些原生的API。

现在我们理解了RN的定位,让我们对比一下RN和Hybrid的渲染情况:

1 OQnf lW2VRs2m3f3i3FtHw 1689609128872 20

从上图可以看出,使用Cordova的App是放在webView里的。这更像是一个在App里的浏览器。用web的概念说,这更像一个web app里的iFrame。

左边展示了React Native是怎么构建的,在RN中的每个组件,比如:Text、Button、Image等都有一个相对应的原生组件。所以和很多新接触RN开发者想的不一样,RN并没有编译成原生代码,它做了个JS组件和原生组件的映射。

当我们这样在Render函数里写了个RN组件:

before

背后部分预先写好的原生组件会是这样的:

after-wow magic!

如果我们用iOS来举例,这些是RN里面的原生组件代码:

and more code

总的来说,RN团队已经为我们创建和映射了所有的原生组件。我们需要去做的只是去基于RN组件和库写JavaScript代码。其他的部分对一般开发者来说都是不可见的,像魔法一样。

所以,看上去我们只用写js代码就可以了。让我们一步步来解释一下这个架构是怎样的。从Bridge开始是最合适的:

RN可以分成JS 和 Native两部分,这两部分都有自己的线程

线程通信是通过Bridge通信,传输的是JSON信息,其中包含了module id,method id和一些需要的数据。这两边并不能直接地感知对方,也不能共享相同的内存。

communication RN

这有点像不同服务器之间通信,如果你有用不同语言写的后台服务,你将怎么在他们之间通信呢?

很多人认为队列是一个很好的解决方案。你发送JSON/XML队列消息,这个消息遵从相应的协议,并且每个服务都知道怎么去读取和解析成对应的数据和行为。这个队列就很像RN里面的Bridge。

communication Server

这里有个例子展示通信是怎样进行的,信息被发送到Bridge。从创建新的View和样式并在屏幕上显示,到在移动端设置子组件和进行一些操作:

communication RN

如果想要在console里看到Bridge的消息,只需要把下面这个代码片段放到index..js里就可以了

communication RN

现在我们知道了RN是怎么通信的,并且开始揭晓其中的“黑魔法”。

  • js代码和objective-c之间的通信。RN在ios上使用的是内置的JSCore
  • JS引擎知道如何去更加高效地把JS转换成机器语言。

这也是为什么Android App需要更多的时间去加载,因为需要加载JSCore到项目里。不过在0.60.2之后,RN可以使用Hermes,这对于RN来说是更理想的JS引擎。

第二部分:Bridge是怎么工作的?

在这部分,我们将通过解析从点击App图标到App打开这个过程的流程和其中的一些相关细节。

imgs 1 Ky7O5Ih3cw9oMSxbjfgysQ

为了理解RN是怎么在背后创建View的,我们先需要解释一些基础概念:

  1. UIManager:在Native侧,是在iOS/Android里主要运行的线程。只有它有权限可以修改客户端UI。
  2. JS Thread:运行打包好的main.bundle.js文件,这个文件包含了RN的所有业务逻辑、行为和组件。
  3. Shadow Node/Tree:在Native层的一个组件树,可以帮助监听App内的UI变化,有点像ReactJS里的虚拟Dom和Dom之间的关系。
  4. Yoga:用来计算layout。是Facebook写的一个C引擎,用来把基于Flexbox的布局转换到Native的布局系统。

理解了上面的一些基础概念,让我们来看下打开App时,每一步发生了什么:

  1. 用户点击App的图标

  2. UIManager线程:加载所有的Native库和Native组件比如 Text、Button、Image等

  3. 告诉Js线程,Native部分准备好了,Js侧开始加载main.bundle.js,这里面包含了所有的js和react逻辑以及组件。

  4. Js侧通过Bridge发送一条JSON消息到Native侧,告诉Native怎么创建UI。值得一提的是:所有经过Bridge的通信都是异步的,并且是打包发送的。这是为了避免阻塞UI,举个栗子:

1_DkhJw_A_amvOfZ_xrpWIHg

  1. Shadow线程最先拿到消息,然后创建UI树

  2. 然后,它使用Yoga布局引擎去获取所有基于flex样式的布局,并且转化成Native的布局,宽、高、间距等。。

  3. 现在UIManager执行一些操作并且像这样在屏幕上展示UI:

    1_T4wuCEqo43VVKWR56gai3Q

这些就是启动App是的主要步骤。

RN基于这个架构有以下优点:

  • UI不会被阻塞:用户感觉到更加流畅
  • 不需要写Native侧的代码:使用RN库的话,很多代码可以只写JavaScript的
  • 性能更加接近Native
  • 一些都完成了。开发者不用去控制并且完全了解它

但是,有优点肯定也会有缺点。下面我们介绍一下能够解决现有缺点的新架构:Fabric。

第三部分:Bridge的优缺点和Fabric架构

我们已经讨论了RN的当前架构,是时候说一下其中的缺陷。可以看一下Facebook的React团队负责人在她博客里提到的。

当前架构的缺点是:

  • 有两个不同的领域:JS和Native,他们彼此之间并不能真正互相感知,并且也不能共享相同的内存。
  • 它们之间的通信是基于Bridge的异步通信。但是这也意味着,并不能保证数据100%并及时地到达另一侧。
  • 传输大数据非常慢,因为内存不能共享,所有在js和native之间传输的数据都是一次新的复制。
  • 无法同步更新UI。比方说有个FlatList,当我滑动的时候会加载大量的数据。在某些边界场景,当有用户界面交互,但数据还没有返回,屏幕可能会发生闪烁。
  • RN代码仓库太大了。导致库更重,开源社区贡献代码或发布修复也更慢。

不过不要误会,Facebook自己也正在使用React Native开发各种app,服务百万级的日活用户。同时也有其他有名的公司在生产环境中使用基于当前架构的RN,比如Wix、Bloomberg、Tesla、Zynga等等。

RN开发团队正着手去解决上面提到的缺点。

这是之前的RN架构:

imgs 1 eCTd2Ymrxse2TKYe1k4Tew

这是新的架构图:

imgs 1 Vjsy fCUFxle UvtjZ8D2Q

让我们来解释一下这些新的概念:JSI,Fabric,Turbo Modules,和 CodeGen。

  1. JSI(将会替换Bridge) -- 为了让JS和Native能够互相感知。将不再需要通过Bridge传输序列化JSON。将允许Native对象被导出成Js对象,反过来也可以。两侧也会导出可以被同步调用的API。实际上,架构的其他部分都是基于这个之上的(Fabric,Turbo Modules等,这些下面会解释)

  2. Fabric -- UIManager的新名称,将负责Native端渲染。和当前的Bridge不同的是,它可以通过JSI导出自己的Native函数,在JS层可以直接使用这些函数引用,反过来,Native层也可以直接调用JS层。这带来更好更高效的性能和数据传输。

  3. Turbo Modules。记得上面的Native组件吗?Text、Image、View,他们的新名字叫Turbo Modules。组件的作用是相同的,但是实现和行为会不同。第一,他们是懒加载的(只有当App需要的时候加载),而现在是在启动时全部加载。另外,他们也是通过JSI导出的,所以JS可以拿到这些组件的引用,并且在React Natvie JS里使用他们。尤其会在启动的时候带来更好的性能表现。

  4. CodeGen -- 为了让JS侧成为两端通信时的唯一可信来源。它可以让开发者创建JS的静态类,以便Native端(Fabric和Turbo Modules)可以识别它们,并且避免每次都校验数据 => 将会带来更好的性能,并且减少传输数据出错的可能性。

  5. Lean Core -- 是对React Native库架构的变化。目的是减轻lib的负担,并帮助社区更快地解决更多pull request。Facebook正在把库的某些部分拆分出来,通过关注Github就可以看出他们在这方面的行动了。比如这个:

    imgs 1 yD11dL03vWJ nknV3AXoaw

顺便一提,这里有个例子,你可以在Chrome里尝试一下,这是JSI如何工作和导出对象的灵感来源:

在Chrome里打开开发者界面,然后输入console.log,回车。你将看到native code。这说明console.log其实是一个Native的函数。

在Chrome里打开开发者界面,然后输入console.log,回车。你将看到native code

接下来我们再对比一下使用新架构,App启动的流程是怎么样的。

  1. 用户点击App的图标

  2. Fabric加载Native侧(没有Native组件)

  3. 然后通知JS线程Native侧准备好了,JS侧会加载所有的main.bundle.js,里面包含了所有的js和react逻辑+组件

  4. JS通过一个Native函数的引用(JSI API导出的)调用到Fabric,同时Shadow Node创建一个和以前一样的UI树。

  5. Yogo执行布局计算,把基于Flexbox的布局转化成终端的布局。

  6. Fabric执行操作并且显示UI==>

    imgs 1 T4wuCEqo43VVKWR56gai3Q 5207290

为了完成整个流程,我们几乎做了同样的事情,但是没有了Bridge,现在我们可以有更好的性能,我们可以用同步的方式进行操作,甚至可以对UI上的同步操作进行优先级排序。启动时间也将更快,App也将更小。

所以,我们什么时候可以上手使用这些东西?

可以在Github上关注它们各自的更新情况:

  1. JSI
  2. Fabric
  3. Turbo Modules
  4. Lean Core
  5. CodeGen

按理说,大多数的更改将逐步完成,并有希望在2019第四季度或2020第一季度发布。

部分参考文档,便于更深刻地理解:

本文首发于 一粟(https://www.zeyio.com),欢迎转载,但是必须保留本文的署名和链接。

本文永久链接:https://www.zeyio.com/rn-fabric-bridge/