-
Notifications
You must be signed in to change notification settings - Fork 25
Description
跨平台其实是一个老生常谈的话题,技术方案也是历经变迁,但始终热点不断,究其原因有二:
- 首先,移动端原生技术需要配备 iOS 和 Android 两套团队和技术栈,且存在发版周期限制,开发效率上存在天然缺陷;
- 其次,原生跨平台技术虽然「出道」较早,但是各方案都难以做到完美,因此也没有大一统的技术垄断。
——因此,周期性地就有问题:「XXX 跨端项目现在是否凉了」、「XXX 跨端项目以后发展前景如何」?
其实这些问题都有一个统一的回答:看(业务、团队等)场景,看(业务、团队等)需求。每一种原生跨端方案在一定历史阶段内,都有其存在的意义和价值。就此,我不再聚焦「React Native (营销或前景上)是否凉了」,仅从技术层面进行简单分析 React Native 究竟完了没完。
我们先来简单 recap 一下跨端技术发展之路,总结如下图:
早期出现了 Cordova、Ionic 等框架,它们本质上都是使用 HTML、CSS 和 JavaScript 进行跨平台原生应用的开发。该方案说到底是在 iOS 和 Androd 上运行 Web 应用,因此也存在较多问题,比如:
- JavaScript Context 和原生通信频繁,导致性能体验较差;
- 页面逻辑由前端负责,组件也是前端渲染,也造成了性能短板;
- 运行 JavaScript 的 WebView 内核在各平台上不统一;
- 国内厂商对于系统的深度定制,导致内核碎片化。
这里不再赘述,我们主要聚焦一下 React Native。因为 hybrid 时代方案缺陷,新一代的 Hybrid 跨平台方式,以 React Native 为代表的方案就诞生了,这种方案主要思想是:开发者依然**使用 Web 语言(如 React 框架或其他 DSL),但渲染基本交给原生平台处理。**这样一来,在视图层面就可以摆脱 WebView 的束缚,保障了开发体验和效率,以及使用性能。我把这种技术叫作基于 OEM 的 Hybrid 方案。
React Native 脱胎于 React 理念,它将数据与视图相隔离,React Native 代码中的标签映射为虚拟节点,由原生平台解析虚拟节点并渲染出原生组件。美好的愿景是:开发者使用 React 语法,同时开发原生应用和 Web 应用,其中组件渲染、动画效果、网络请求等都由原生平台来负责。
RN现在主要有3个线程
- JS thread。JS代码执行线程,负责逻辑层面的处理。Metro(打包工具)将React源码打包成一个单一JS文件(就是图中JSBundle)。然后传给JS引擎执行,现在ios和android统一用的是JSC。
- UI Thread(Main Thread/Native thread)。这个线程主要负责原生渲染(Native UI)和调用原生能力(Native Modules)比如蓝牙等。
- Shadow Thread。 这个线程主要是创建Shadow Tree来模拟React结构树。Shadow Tree可以类似虚拟dom。RN使用Flexbox布局,但是原生是不支持,所以Yoga就是用来将Flexbox布局转换为原生平台的布局方式。
React Native 主要由:
- JavaScript
- C++ 适配层
- iOS/Androd
三层组成,最重要的 C++ 层实现了动态链接库,起到了衔接适配前端和原生平台作用,这个衔接具体指:使用 JavaScriptCore 解析 JavaScript 代码(iOS 上不允许用自己的 JS Engine,iOS 7+ 默认使用 JavaScriptCore,Android 也默认使用 JavaScriptCore),通过 MessageQueue.js 实现双向通信,实际上通信格式类似 JSON-RPC。
这样的效果显而易见,通过前端能力,实现了原生应用的跨平台,快速编译、快速发布。
但是为什么有人觉得 React Native 要完了呢?
对 React Native 来说,上述数据通信过程是异步的,通信成本很高。除此之外,目前 React Native 仍有部分组件和 API 并没有实现平台统一,也在一定程度上需要开发者了解原生开发细节。正因如此,社区上也出现了著名文章《React Native at Airbnb》,文中表示 Airbnb 团队在技术选型上将会放弃 React Native。
这...React Native 要凉?
在我看来,像 airbnb 那样放弃 React Native,拥抱新的跨平台技术并不是每个团队都有实力和魄力施行的,而改造 React Native 是另外一些团队做出的选择。
比如携程的 CRN(Ctrip React Native)以及美团的 MRN。他们在 React Native 基础上,抹平了 iOS 和 Android 端组件开发差异,做了大量性能提升的工作。更重要的是,依托于 CRN,它在后续的产品 CRN-Web 也做了 Web 支持和接入。
另外更重要的是,**React Native 也在成长。**上文我们提到,React Native 通过数据通信架起了 Web 和原生平台的桥梁,而这个数据通信方式是异步的。React 工程经理 Sophie Alpert 将这种这样的设计获得了线程隔离的便利,具备了尽可能的灵活性,但是这也意味着 JavaScript 逻辑与原生能力永远无法处在同一个时空,无法共享一个内存空间。
基于这个问题,新的 React Native 技术架构将从三个方面进行革新。
- 改变线程模型(Threading Model),以往 React Native 的 UI 更新需要在三个不同的线程进行,新的方案使具有高优先级更新的线程,直接同步调用 JavaScript;同时低优先级的 UI 更新任务不会占用主线程。
- 引入异步渲染能力,实现不同优先级的渲染,同时简化渲染数据信息。
- 简化 Bridge 实现,使之更轻量可靠,使 JavaScript 和原生平台的调用更加高效。
新架构如下图:
举个例子,新的架构这些改造能够使得“手势处理”——这个 React Native 老大难问题得到更好的解决,比如新的线程模型能够使手势触发的交互和 UI 渲染效率更高,减少异步通信更新 UI 成本,使视图尽快响应用户的交互。
上述重构的核心之一其实是使用基于 JavaScript Interface (JSI) 的新 Bridge 方案来取代之前的 Bridge 方案。新的 Bridge 方案由两部分组成:
- Fabric,新的 UIManager;
- TurboModules,新的原生模块
其中 Fabric 运行 UIManager 直接用 C++ 生成 Shadow Tree,而不需要走一遍老架构的 React → Native → Shadow Tree → Native UI 路径。这就减少了通信成本,提升交互性能。这个过程依赖于 JSI,JSI 并不和 JavaScriptCore 绑定,因此我们可以实现引擎互换(比如使用 V8,或任何其他版本的 JavaScriptCore)。同时,JSI 可以获取 C++ Host Objects,并调用 Host Objects 上的方法,这样能够完成 JavaScript 和原生平台的直接感知,达到“所有线程之间的互相调用操作”,因此我们就不再依赖“将传递消息序列化,并进行异步通信”了。这也就消除了异步通信带来的拥塞等问题。新的方案也允许 JavaScript 代码仅在真正需要时加载每个模块,如果应用中并不需要使用 Native Modules(例如蓝牙功能),那么它就不会在程序打开时被加载,这样就可以提升应用的启动时间。
我们再来看看最炙手可热的 Flutter,Flutter 采用了 Dart 编程语言,它在技术设计上不同于 React Native 的一个显著特点是:Flutter 并非使用原生平台组件进行渲染。比如在 React Native 中,一个 <view>
组件会被最终编译为 iOS 平台的 UIView Element 以及 Android 平台的 View Element,但在 Flutter 中,Flutter 自身提供一组组件集合,这些组件集合被 Flutter 框架和引擎直接接管。如下图(出自 Cross-Platform Mobile Development Using Flutter):
Flutter 组件依靠自身高性能的渲染引擎进行视图的渲染。具体来说,每一个组件会被渲染在 Skia 上,Skia 是一个 2D 的绘图引擎库,具有跨平台特点。Skia 唯一需要的就是原生平台提供 Canvas 接口,实现绘制。
目前来看,Flutter 具备其他(主流)跨平台方案所不具备的技术优势,它是更底层,真正意义上的跨端,未来前景大好。但作为后入场者,也存在生态小、学习成本高等障碍,也正因为更底层,存在需要通过 bridge,调用原生平台能力的成本困扰。
总结
说了这么多,讲了一堆「技术原理」,那么到底 React Native 凉没凉呢?
其实这不是任何一个「专家」或背书的团队应该回答你的问题,需求不同、立场不同,它们都不会完全靠谱。当你了解个各个方案的技术原理,掌握了 React Native 发展路线(包括新版本的技术重构)等重要信息,结合自己业务和团队的需求、痛点,进行选择?