1. 跨端架构的意义
在《The Pragmatic Programmer》(中文翻译为《程序员修炼之道》)中,作者提了一个 DRY(Don’t Repeat Yourself)原则,主要指在程序设计以及计算中避免重复代码,因为这样会降低灵活性、简洁性。 把一切重复的代码抽象出来复用,当需要修改的时候只需要修改一次。 这里的复用有如下一些级别:函数级复用、对象级复用、模块级复用、类库级复用、框架级复用。 咱们今天聊的跨端是更高层面的复用,是端的复用。
现实中我们常见的工作方式是安卓的同学写安卓,iOS 的同学写 iOS,前端写 Web,小程序,H5 等等。 随着整体技术的进步,前端技术的发展,大前端的兴起,跨端实现越来多了,一些跨端的框架层出不穷。最终想要实现的跨端架构希望能做到:一次编写,多端运行
这里的端包括如下:
- PC 端(苹果,Windows、Linux)
- 移动端(安卓、iOS)
- Web 应用、插件
- 小程序(微信小程序,企业微信小程序、钉钉小程序、快手小程序)
- H5
以上跨端的逻辑分为三个层面
- 硬件形态,或者说是设备,如 PC 电脑,移动设备、物联网设备
- 相同硬件形态下的不同平台,或者说是跨操作系统,如 PC 下的 Mac、Windows
- 相同平台下不同的应用或应用中的衍生应用,如 iOS 平台下浏览器应用(H5),微信小程序,支付宝小程序等
应用运行在某个操作系统之上,操作系统位于硬件设备与用户之间,是两者沟通的桥梁。不同的硬件有不同的架构和指令集,其对应也会有不同的操作系统。不同的操作系统其对应的执行程序结构不同,如 Windwos 下的 exe 结尾的程序不能在 Mac 下执行。这就是所谓的平台。
那么如何跨平台?
跨平台也是跨端的一部分,我们常见的跨平台应用,如浏览器,它所实现的跨平台是指运行在浏览器中的网页是跨平台的,而浏览器本身并不会跨平台,浏览器的生产厂商根据不同的平台,不同的操作系统分别实现了对应的版本,在应用层面抹平了平台和操作系统的差异,实现了跨端的目的。
浏览器在实现过程中会提供一个容器给到开发者,屏蔽平台差异,提供统一的 API 接口,让一份代码可以在不同的平台运行。 与浏览器类似的还有 Docker、JVM、Node 等。
在浏览器跨端的基础上,Electron 整合 Chromium 的渲染引擎、NodeJS 和用于调用系统本地功能的 API,使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序,其可构建出兼容 Mac、Windows 和 Linux 三个平台的应用程序。
在跨平台之外,我们还需要跨应用,如在不同的小程序、H5 之间,既然是应用,那就一定会包含界面,业务逻辑,如何只写一次业务逻辑,在不同的应用中执行,在代码重复层面追求更极致的体验,遵从 DRY 原则,让代码一次编写,多端运行。
我们在追求跨端的同时,也希望跨端的体验能趋近于原生的体验。
2. 跨端架构的本质
跨端架构的本质用稍微文艺一点的话来说就是:「世上本没有什么岁月静好,只不过是有人替你负重前行」
一个跨端的架构至少包含渲染、逻辑和原生能力支撑、端的构建方式。一般会通过实现自己的容器来抹平(或者兼容)端的差异。
这个容器一般会提供两个能力:一个是渲染,另一个逻辑和原生能力支撑。 看我们常见的几种方案:
2.1 H5 hybrid 方案
浏览器自带跨端属性,通过各平台都有的浏览器,我们可以直接实现跨端,除了浏览器应用,我们也会把浏览器引擎嵌入到 APP 中,部分或全部使用浏览器渲染引擎,常见的如 Webkit、Blink 等。我们通过 Javascript 实现逻辑并且通过 JSBridge 调用 Native 的 API,透出的 API 需要应用在内部定制。我们一般用原生来实现要求高的界面,对于一些比较通用型,展示型的页面完全用 web 来实现,达到跨平台效果,各家的区别在于对于这块的定制和优化到了什么程度。
组成:渲染:WebView,逻辑:JS Engine,底层能力:JSBridge + 原生能力
优点:
- 接入成本极低,基本可以复用前端的技术栈和生态;
- 效率平衡性较好;
- 支持热更新,发版效率高;
缺点:
- 受限于 webview 等原因,动画复杂、无限流类的页面性能较差;
- 首开等对时间要求高的场景不能很好地做到极致;
如:网易云音乐,腾讯 QQ 中的大部分运营功能
2.2 框架 + 原生渲染
以 React Native 和 Weex 为代表的方案,通过结合 Web 的生态和 Native 的组件,尽可能地取长补短,让 JS 执行代码后用 Native 的组件进行渲染,实现了远超 webview 的效果。以 React Native 为例
组成:渲染:原生组件;逻辑:JS Engine + JSI;底层能力:原生组件;
优点:
- 开发成本均衡,大于 hybrid 模式,但是小于原生开发模式,一次开发,多端可用;
- 学习成本低,前端同学可以较快上手,上手速度快;
- 复用了前端生态,生态成熟,遇到问题,较容易解决;
- 有 Facebook 背书,发展相对靠谱;
缺点:
- 对于不同的平台特性的内容,需要有一些兼容的写法,甚至各平台各写一套;
- 交互复杂时,可能存在性能问题,新的架构已经大部分缓解了这个问题;
- 使用各端的原生组件渲染,相同代码渲染效果可能不一致性,已在尝试统一渲染。
使用 RN 的 APP: Facebook、youtube、discord、QQ、百度等等 使用 Weex 的 APP: 淘宝、天猫、饿了么等
2.3 框架 + 自渲染引擎
以 Flutter 为代表,Flutter 将代码编译成原生代码,并且直接在各个平台中使用高效渲染引擎 Skia 进行渲染,没有桥接,不调用平台相关控件。Flutter 没有直接借用原生能力去渲染组件,而是利用了更底层的渲染能力,自己去渲染组件。这种方式的链路会比 RN 的链路跟短,性能也会更好,同时在保证多端渲染一致性上效果更优。
组成:渲染:Skia;逻辑:Dart VM;底层能力:原生组件
优点:
- 性能好,更接近原生;
- 跨平台体验优秀,跨多种平台,减少开发成本;
- UI 跨平台稳定;
- 同时支持 JIT 和 AOT 两种编译方式的特性,在不同场景下可以使用不同的编译方式。
缺点:
- UI 跨平台,但原生能力没有,脱离不开原生,开发人员需要具备原生(Android、iOS)基础开发能力,兼容适配性较差;
- 稳定性、可能会因为引入了 flutter 而导致线上的 crash 率增加;
- 代码可读性较差,Widget 的类型难以选择;
- 生态中的 SDK,各种第三方包鱼龙混杂,没有一个官方的标准
除了最开始的 Android 和 iOS 跨平台支持,最新已经开始支持 Web 和 MacOS,未来还会继续支持 Win 和 Linux 平台的。在 Web 场景下,目前 Flutter 只能说可以用,但是还有挺多需要优化的空间,比如编译后 Web 文件大小,特定场景下的性能以及不同浏览器内核的兼容等等。
使用 Flutter 的应用:如钉钉(定制了较多的功能)、美团外卖、马蜂窝等
2.4 DSL 编译 + 混合渲染
该方案提供自定义 DSL 静态编译转化成目标源代码,包括 iOS、Android,H5 以及中国特色的各小程序平台。主要包括 uni-app、taro、Chameleon、Rax 等等。但各家实现不同,支持的平台类型也不一致,以 uni-app 为例:
组成:渲染:混合渲染、weex原生渲染、webview渲染,小程序和 app-vue 页面属于混合渲染,app-nvue 页面全部是 weex 原生渲染,H5 全部为 webview 渲染;逻辑:JS Engine + VUE; 底层能力:原生组件、原生插件;
优点:
- 支持多种小程序,多端;
- 开发成本低、学习成本小,本质上就是在写前端;
- 插件多,但是以个人开发者居多,质量参差不齐,没有保证;
缺点:
- 什么都想要,而什么都没有到极致,如果只是做一个能用的应用,是合适的,如果对于性能要求高,或者有比较复杂的交互,需要谨慎调研考虑一下;
- 兼容性问题依然有很多小细节问题,存在多端同时上线,某一端存在 bug 的情况;
- 原生功能依赖于 nvue ,对于没有提供的原生功能,需要对应的原生开发同学来开发;
我们要做到应用的跨端,在 PC 端相对好确定一些,以前端为主,客户端辅助实现部分前端薄弱的部分,如安装过程和一些和操作系统打交道或对性能要求比较高的部分。我们常用的 PC 端架构可以基于 Electron 、Tauri,在多平台客户端和 Web 端实现跨端。
在移动端,则更复杂一些,不仅要跨 iOS 和安卓这种操作系统级,还要跨微信小程序、支付宝小程序这种应用的衍生应用。下面我们聊聊一些有人在用的方案。
3. 实际落地的几种方案
在考虑实际落地之前需要明确一下是在什么层面的跨端,最理想的是跨全端,即跨硬件平台,在 PC、移动端设备,另一种是分两种,大屏和小屏,大屏主要是针对 PC 端、小屏主要是针对移动端。
如果是跨全端,不仅仅要考虑技术实现,从产品、到设计都要考虑,产品要考虑针对不同的端的应用场景,设计要考虑不同的屏大小下的体验效果和交互方式。这里我们只考虑移动端的情况。
3.1 Qunar 的 React Native 优先的多端统一化方案
Qunar 方案的主要逻辑是基于 Qunar 已有的 RN 技术栈,已经解决了 iOS 和 Android 的跨端问题,在此条件下,其问题变成了如何将 RN 转换为 H5 和各小程序。业内没有现成的方案,只能曲线救国,分别处理:
- 对于 RN 到 H5,选择使用 Twitter 开源的 react-native-web,将 RN 代码运行在 H5 上,这个把 RN 的组件和 API 都用 H5 实现适配一遍,适配其行为和默认样式,在打包的时候使用 webpack 的别名机制将用到的组件替换成 react-native-web 里的对应组件。react-native-web 对原项目没有侵入性,无需改动原来的代码,只需在项目中加入一些 webpack 构建配置即可构建出运行出和 React Native 应用一致效果的 Web 应用。
- 对于 RN 到 小程序,选择使用 Remax 组件实现一套 RN 的组件库,借用 remax 来适配到多端。Remax 的运行时本质是一个通过 react-reconciler 实现的一个小程序端的渲染器。Remax 通过 react-reconciler 生成一份自定义的 VNode Tree,再遍历 Tree 递归模版(微信小程序模板不支持递归,Remax 会为微信小程序生成一个 20 层的模板调用)渲染出对应的小程序页面。在生成之前 Remax会 为每个组件生成一份模版,然后把这份模版写入每个 page 页面里。由于小程序本身 View 与 JS 分离,因此在拿到 Vnode 时,需要通过小程序自身的 setData 触发小程序渲染。
3.2 Flutter 全平台方案
Flutter 可以理解为使用 Dart 语言定义了一套和原生一样的图形系统,其底层使用和 Android 原生一样的 Skia 引擎,安卓下直接用系统引擎,苹果生态下用自带的 SKia,这样就完全避免了 RN 中 JS Core 和原生模块通信造成的各种开销。
Flutter 这种自实现的引擎能带来目前体验最好的两端一致性,同一套代码,在 Android 和 iOS 上执行,从业务逻辑到页面布局再到最终渲染,都是在 Flutter 内部完成,通过 Flutter 实现的功能,在不同系统手机上的呈现效果是高度一致的。
Flutter 在 Android 和 iOS 上对跨端的支持较好,现在也有较多的业务在用 Flutter 完成这两端的跨端。对于 H5 而言,2019 年 2 月 Flutter1.2 版本和后面 5 月发布的 1.5 版本都主要支持了 Web ,但是到现在为止,Flutter 在 Web 端目前只支持 Dart–>JS 的转换,以及 UI 层的对齐,在工程化和性能优化方面做的工作并不多。Google 官方对 Flutter Web 性能优化所做的事项还比较少,编译输出的页面存在较大的性能问题,主要体现在以下两方面:
- 首屏渲染时间长。即使使用了 FutureBuilder 把业务代码拆分成 xxx.part.js 之后,main.dart.js 体积依然维持在 1.1M。单一文件加载、解析时间过长,且静态资源缺少 CDN 化的支持,势必会影响首屏的渲染时间。
- 滚动性能较差。 Flutter Web 自身实现了一套页面滚动机制,在页面滚动过程中,会频繁的创建 Canvas,最终导致滚动性能问题,甚至引起页面 Crash。
在阿里钉钉,基于 Flutter 构建的跨四端研发框架 Dutter,自己解决了数据通信问题、实现了自己的组件库,目前核心组件可以做到四端兼容,具体可以见:钉钉 Flutter 跨四端方案设计与技术实践
3.3 Hybrid 方案
这种反而在大厂是更常用的方案,其主要原因是大厂一般会有自己的框架团队,能够做比较多的定制,甚至有些把 Flutter 改吧改吧,自己实现一套,或者以容器化的方式实现自己的 Hybrid 方案。具体实现就不介绍了,这里主要介绍一下常用的一些性能优化的点,Hybrid 方案的性能优化的关键点就六个字:更早,更近、更快。
- 更早:提前创建或初始化一些基础组件,如 WebView,减少用户体验层面等待的时间,让用户感觉更快了,也就是我们常说的预加载;
- 更近:把资源提前缓存在本地,这里的资源可以是 Web 的资源,也可以是常用的配置数据等等,本地可以是客户端,也可以是移动端,如我们常见的「离线包」,特别是一些大流量的应用,一些资源的提前下发可以解决 CDN 等的突发流量等问题;
- 更快:通过 Native 的方式替换一些跨端方案中的薄弱点,如一些网络控制的能力,一些视频播放或加载速度的优化等等
4. 小结
为什么标题中带一个 2022 呢? 因为技术是不断演进的,是不断发展的,今年的方案不一定适用于明年,期望有更好的方案出现。
写了这么多,把跨端的问题粗略的过了一遍,给自己温习一下,也分享一下。
任何跨端都是有成本的,当你选择跨端的时候,需要想的第一件事情,是否有必要这么做?ROI 如何?
跨端的问题很多最终都需要回归到当前端来解决,特别是一些对性能,对底层要求比较高的问题往往要在端来解决。
整体来看,跨端技术选型需要考虑如下 4 个问题:
- 战略和业务的问题,从公司产品战略和业务产品的角度探讨是否有必要跨端,如你的业务是否有强依赖的多端需求,各端的用户体量是否值得有如此投入?还是只要一端强就可以了?
- 人和组织的问题,在确认有跨端的强需求后,再看是否有合适的人和人才梯队来构建你想要的跨端架构,并且在确定跨端架构后考虑关于分工的问题,如一部分同学(如原移动端的同学)负责框架和能力,一部分的同学(如前端的同学)负责业务。在考虑现有人和组织的问题的时候,考虑一下后续人才招聘和团队人才密度的情况;
- 生态的问题,生态的问题会决定研发效率,是否有成熟的生态,是否有前从把坑都踩过了,当遇到某个场景是否有现成的解决方案或者类似的解决方案等等;
- 性能和体验的问题,随着业务的复杂,交互,场景也会越发的复杂,当遇到因复杂交互,或者复杂业务场景引起的性能问题时,是否有成熟的解决方案,或者退一步,是否可以解决?在多端一致性的问题上,是否能满足需求,或者兼容处理的成本有多高?
最后,祝大家国庆节快乐~
参考文档: