React Native原理和架构
发展历程
原生开发(Native APP)
Native APP 基于智能手机本地操作系统如iOS、Android、WP并使用原生程式编写运行的第三方应用程序,一般开发的语言为Java、C++等。,有很强的交互是一个完整的App,可拓展性强。需要用户下载安装使用。
优点:
- 打造完美的用户体验,用户留存率高
- 性能稳定,操作速度快,上手流畅
- 访问本地资源(通讯录,相册),可以调用移动硬件设备(摄像头、麦克风等)
缺点:
- 开发成本高(每种移动操作系统都需要独立的开发项目,不同平台有不同的开发语言和界面适配)
- 维护成本高(下载是用户控制的,例如一款App已更新至5.0版本,但仍有用户在使用3.0,2.0版本,需要更多的开发人员维护之前的版本)
- 更新缓慢,应用商店发布审核周期长,安卓平台大概要1~3天,而iOS平台需要的时间更长
混合开发(Hybrid APP)
Hybrid APP 就是原生开发和web的混合开发。在原生APP中内置浏览器,合适的功能页面采用网页的形式呈现。比如京东的某些营销页面,今日头条的某些新闻页面、微信的腾讯新闻的内容页面等。
优点:
- 学习成本低
- 天然跨平台
- 无审核热更新
- 可扩展
缺点:
- 性能很差
##### 跨平台开发框架
本质上,跨平台开发是为了增加代码复用,减少开发者对多个平台差异适配的工作量,降低开发成本,提高业务专注的同时,提供比web更好的体验。想要解决用户体验的问题,基本还是需要回到 native 来进行开发,但是这种行为必然会与平台绑定。其中一件事情就是自动将某个平台的代码转换到另外的平台上去。
前移动端跨平台开发中,备受关注的方案大致归纳为以下几种情况:
1)React Native、Weex均使用JavaScript作为编程语言,目前JavaScript在跨平台开发中,占据半壁江山,大有“一统天下”的趋势;
2)Flutter是Google跨平台移动UI框架,Dart作为谷歌的亲儿子,毫无疑问Dart成为flutter的编程语言。作为巨头新生儿,在flutter官网也可以看出,flutter同样“心怀天下”(可支持Web端、Android端、iOS端等)。
React Native的原理与特性介绍
理念架构
“Learn once, write anywhere” , React Native 使用 React 的设计模式,但UI渲染、动画效果、网络请求等均由原生端实现。开发者编写的JavaScript代码,通过 React Native 的中间层转化为原生控件和操作,比ionic等跨平台应用,大大提高了的用户体验。
总结:React Native是通过js来调用Native端组件
在 React Native 里面,真正有三个重要的线程在执行,他们分别是 Shadow thread、UI thread 和 JS thread。
JS thread。是读取和编译所有 JavaScript 代码、处理应用程序大部分业务逻辑的地方。
- Metro(打包工具)将React源码打包成一个单一JS文件(就是图中JSBundle)。然后传给JS引擎执行(现在ios和android统一用的是JSC)。
- JS thread 负责 JS 和原生代码的交互,因为 JS 是单线程模型,所以需要一个单独的线程来驱动,并且 JS 和 Native 交互(Bridge)是异步的。
UI Thread(Main Thread/Native thread)。这个线程主要负责两部分:
- 原生渲染(Native UI):负责页面的交互,以及当显示、更改UI时,完成控件绘制逻辑。
- 调用原生功能(Native Modules):比如蓝牙等。当我们使用应用程序时,所有本地模块都会启动。这意味着即使不需要使用蓝牙模块,React Native 也将始终激活它。
Shadow Thread。 是 React Native 计算布局的地方。
- 这个线程会创建Shadow Tree来模拟React结构树。Shadow Tree可以类似虚拟dom。
- 它使用 Facebook 自己的名为 Yoga 的布局引擎来计算 flexbox 布局,然后将结果发送到 Native UI(RN使用Flexbox布局,但是原生是不支持,所以Yoga就是用来将Flexbox布局转换为原生平台的布局方式)。
JavaScriptCore
JavaScriptCore 是 JavaScript 引擎,通常会被叫做虚拟机,专门设计来解释和执行 JavaScript 代码。在 React Native 里面,JavaScriptCore 负责 bundle 产出的 JS 代码的解析和执行。
JS Engine
React Native 需要一个 JS 的运行环境,因为 React Native 会把应用的 JS 代码编译成一个 JS 文件(x x.bundle),React Native 框架的目标就是解释运行这个 JS 脚本文件。
如果是 Native 拓展的 API,则直接通过 bridge 调用 Native 方法,最基础的比如绘制 UI 界面,映射 Virtual DOM 到真实的 UI 组件中。
脱离 React Native,纯原生端是如何与 JS 交互的?来看下 iOS 里面是如何实现的。
在 Native 创建一个 JS 上下文:
// 创建一个ctx的JS上下文
JSContent *ctx = [[JSContent alloc] init];
// 创建一个变量name
[ctx evaluateScript:@"var name = 'Hellen'"];
// 创建一个方法
[ctx evaluateScript:@"var hello = function(name) { return 'hello ' + name }"];
Native 调用 JavaScript 方法:
// 通过ctx上下文对象,获取到hello方法
JSValue *helloFUnction = ctx[@"hello"];
// 运行js方法
JSValue *greetings = [helloFunction callWithArguments:@[@"bytedancers"]; // hello bytedancers
所以,JavaScript 代码只要将变量暴露在 JS 上下文全局,Native 就能获取到,并运行 JS 的代码。
JavaScript 调用 Native,首先需要在 Native 端,将一个变量暴露在 JS 上下文全局,在 JavaScript 全局变量里面就能获取到并执行这个方法:
ctx[@"createdByNative"] = ^(NSString *name) {
// do something
return someResult
}
React Native 同样借助 JS Engine 的能力,基于 JavaScriptCore 来执行 JS,但是是通过 Bridge 来进行交互的,JS 不会直接引用 Native 层的对象实例,Native 也不会直接引用 JS 层的对象实例(在 React Native 里所有 Native 和 JS 互调都是通过 Bridge 层的几个最基础的方法衔接的)。
Hermes Engine
Hermes 是 Facebook 在 2019 年发布的新一代 JS Engine,Hermes 是一款小巧轻便的 JavaScript 引擎,专门针对在 Android 上运行 React Native 进行了优化:应用启动时间减少、减少内存使用量并缩小应用程序大小,此外因为它采用 JavaScript 标准实现,所以很容易在 React Native 应用中集成。
Hermes vs JavaScriptCore vs V8
经过官方的数据验证,Faceback 团队提出的关键性指标相较于原先的 JavaScriptCore 方案都有了显著提高。首先,是产物文件的大小方面,RN 所依赖的必要 so 库,Hermes 比 JavaScriptCore 减少了约 16%,V8 则要远大于 Hermes 和 JavaScriptCore。
热更新
React Native 的产物 bundle 文件,本质上是 JS 的逻辑代码加上 React Native 的 Runtime 的集合,所以在应用一启动的时候就会去获取 bundle 文件,之后解析 bundle 文件,最后再由 JS Engine 去执行具体的业务代码逻辑。这就可以允许开发者在云端去更新 bundle 文件,然后应用启动的时候获取最新的 bundle 文件,这一整个流程下来就实现了热更新。
增量更新(拆包)
对于 React Native 的代码打包之后只会生成一个 Bundle 文件,这里面包含了基础业务逻辑、React Native 的基础库类,所以我们可以把一个包拆分成:一个基础包+ n 个业务包,其中基础包是不变的,这就是 runtime,业务包就是具体的业务,后面如果有更新,也只需要再打出一个业务包就行。
目前行业的解决方案有 facebook 官方提供的 metro bundle:facebook.github.io/metro/
React Native新架构
FB团队逐渐意识到Bridge存在的一些问题,同时也受到Flutter的压力,在2018年提出了新架构:移除了Bridge,取而代之的是一个名为 Javascript Interface (JSI) 的新组件。
新的架构主要由 JSI、Fabric、TurboModules、CodeGen、LeanCode组成。
JSI
JSI(Javascript Interface)是整个架构的核心和基石,所有的一切都是建立在它上面。用于取代原先的 bridge,提高通信效率,JSI 已经跟随 RN 0.59 (JSIExecuter.cpp) 发布。
JSI 本身不是 React Native 的一部分——它是一个统一的、轻量的、通用适用于任何(理论上) JavaScript 虚拟机的接口层。让各种平台可以方便地使用不同的 JavaScript 解析引擎(JavaScript virtual machine 包含 JavaScript Engine)。
当把 JSI 加入到新架构中后,它使得一些真正重要的改进成为可能。
- 第一个改进很直观 —— 有了JSI,JS引擎不再局限于JSC。换句话说,JSC 引擎现在可以与其他具有更好性能的 JavaScript 引擎交换,比如微软的 ChakraCore 和谷歌的 V8等,进一步提高JS解析执行的速度。
- 通过JSI,JS对象可以直接持有C++宿主对象(Host Objects)引用,并调用它们的方法。
宿主对象:由宿主环境提供的对象,对应Native Object。如JavaScript中,浏览器宿主环境中的window对象以及其下边所有的子对象(如bom、dom等等),node宿主环境中的globla及其子对象。
这意味着:自此三个线程通信再也不需要通过Bridge,JavaScript 和 Native 之间真正地相互知晓,并且不再需要通过 JSON 序列化传递消息,这会消除 Bridge 的阻塞问题。不像原来那样用一层 bridge 来排队等待原生层返回的消息,让同步通信成为现实。具体的用法可以看 官方例子。
JS -> JSI -> C++ -> ObjectC/Java
Fabric
Fabric是整个架构中的新UI层,包括了新架构图中的renderer和shadow thread。
下图是旧的通信模型。
三个线程通过Bridge异步通信,数据需要拷贝多份。
有了JSI以后,JS可以直接掉调用其他线程,实现同步通信机制。另外数据可以直接引用,不需要拷贝,于是就变成了下面新的通信模式.
除了同步能力,直接引用,另外一个好处是Fabric现在支持渲染优先级比如React的Concurrent和Suspense模式
下面两张图是从启动到渲染阶段,加入Fabric前后的变化。
改造为Fabric之后
TurboModules
TurboModules主要和原生应用能力相关,对应新架构图上的Native Modules,这部分的优化是:
- 通过JSI,可以让JS直接调用Native模块,实现一些同步操作。比如调用摄像头能力。
- Native模块懒加载。之前RN框架启动的时候会加载所有Native模块,导致启动慢,时间久。现在有了TurboModules后,可以实现按需加载,减少启动时间,提高性能。
CodeGen
通过CodeGen,自动将Flow或者Ts等有静态类型的JS代码翻译成Fabric和TurboModules使用的原生代码。
Lean Core
这部分主要是包的瘦身,以前所有的包都放在RN核心工程里面。现在RN核心只保留必要的包,其他都移到react-native-community 或者拆出单独的组件,比如Webview和AsyncStore。
开发进度
- 2018 年 6 月,Facebook 曾在 宣布了大规模 重构 RN 的计划和路线图;
- 期间,JSI、LeanCore、Fabric、TurboModules一项一项陆续开发完成;
- 2021 年 7 月 14 日,React Native 核心团队的 Joshua Gross 在 Twitter 说,RN 的新架构已经在 Facebook 内部落地了,并且 99%的代码已经开源。
性能评价
重构目的是为了让 RN 更轻量化、更适应混合开发,接近甚至达到原生的体验。具体包括以下几个方面:
- 改变线程模型。UI 更新不再同时需要在三个不同的线程上触发执行,而是可以在任意线程上同步调用 JavaScript 进行优先更新,同时将低优先级工作推出主线程,以便保持对 UI 的响应。
- 引入异步渲染能力,允许多个渲染并简化异步数据处理。
- 简化 JSBridge,让它更快更轻量。
这次升级过后,RN 在性能上能够追平 Flutter。首先,JavaScript 和 Dart 语言上都支持了 AOT 预编译,打个平手。其次,JavaScript 和 Dart 和底层交互都是通过 C++ 进行的,也是打个平手。最后,RN 原生组件绘制有平台的优化加成, 相对于 Flutter 自绘引擎绘制,可能还会好上一些。
除了显着提升线程之间的通信性能外,这种新架构还让我们可以直接控制 Native Modules。也就是说,可以仅在需要时使用原生模块,而不是在启动应用程序时将它们全部激活。
这为应用程序启动时间提供了显着的性能改进。这种新机制有可能在许多不同的用例中使我们受益。例如,现在我们掌握了 C++ 的强大功能,很容易看出 React Native 是如何用于大型系统目标的。
评论 (0)