跳到主要内容

🎯【深入解析】跨端框架的核心技术到底是什么?

· 阅读需 21 分钟
卤代烃

本文是我在学习多个平台 UI 框架后的一些感触,受精力和技术水平所限,文中定有不足之处,请各位大佬多多指教

一、前端三板斧

正式讨论「跨端开发」这个概念前,我们可以先思考一个问题:对大部分前端工作来说,前端主要干些啥?

我个人认为,无论环境怎么变,前端基本上就是做三件事情:

Fetch Data、Manage State、Render Page

  • fetch data(数据获取)
  • manage state(状态管理)
  • render page(页面渲染)

没了。

也许有人觉得我说的太片面,其实我们可以理一理。往近了说,现在知识付费搞的如火如荼,动不动就搞个「XXX 源码解析」,分析一下这些课程的主题和目录,你就会发现基本都是围绕着这三个方向展开讲的;往远了说,我们可以分析一下 Web 前端的发展历程:

  • 1995 年左右,用 HTTP/1.0 拉取数据,用第一版的 JavaScript 管理几个前端状态,用裸露的 HTML 标签展示页面

  • 2005 年左右,用 HTTP/1.1 和 AJAX 拉取数据,用 JavaScript 做做表单画画特效,用 CSS 美化页面

  • 2010 年左右,用 HTTP/1.1 和 AJAX 拉取数据,用 jQuery 操作 DOM 处理前端逻辑,用 CSS 美化页面

  • 2015 年左右,随着 HTML5 标准的推广和浏览器性能的提升,前端开始进入「学不动了」的时代:

    • 在 fetch data 层面,除了 HTTP/1.1 和 AJAX,HTTPS 来了,HTTP/2 来了,WebSocket 也来了
    • 在 manage state 层面,Angular、React 和 Vue 先后出现,从现在看,React 的状态驱动视图的理念直接影响了 Flutter 和 SwiftUI 的设计
    • 在 render page 层面,除了传统的 HTML + CSS,还加入了 CSS3、Canvas 等概念,音视频功能也得到加强
  • 最近几年,网络协议趋于稳定,几年内也不会有啥大的变动;国内 React 和 Vue 的地位基本稳固,一堆前端盯着 GitHub 进度条等版本更新;render 层出了不少幺蛾子,好不容易摆脱了 IE6,又来了各种小程序,同一套业务逻辑写好几遍不经济也不现实,这时候各种跨端方案就整出来了

经过一番分析,这个三板斧理论看上去已经有些道理了,我们顺着这个方向再向底层思考:这三大功能是怎么实现的?

  • fetch data 方向,最后要靠网络协议栈把数据发出去,但是让一个前端直接搞套接字编程是非常不现实的,所以我们需要把网络操作封装为库,让应用层调用
  • render page 方向,最后是把相关图元信息通过各种图形 API(OpenGL/Metal/Vulkan/DirectX)发给 GPU 进行渲染,很多前端的图形学路程最终都止于一个三角形,用这套技术栈去画 UI 也极其不现实,更不要说排版系统这种工程量浩大的工作,所以这些活儿都让相关的渲染引擎做了
  • manage state 方向,你可以用全局变量管理状态,最后的结局一定被同事打爆,现在主流方案都是采用各种框架和 runtime 进行状态管理,而这个 runtime 的宿主环境,往往就是某个语言的虚拟机,同时,fetch data 的起点,也是同一个虚拟机

虚拟机 渲染引擎

经过上面的分析我们可以看出,前端的主要技术核心就两个:虚拟机渲染引擎,这也意味着,如果我们想要搞跨端开发,就必须得统一虚拟机和渲染引擎

二、虚拟机和渲染引擎

1.网页:JS Engine + WebKit

前端三剑客

因为谷歌的 Blink 引擎 fork 自苹果的 WebKit,后文为了描述方便,统一用 WebKit 代替浏览器渲染引擎

网页是成本最低上手最快的跨端方案了。得益于互联网开放式理念,网页天生就是跨端的,无论什么渲染框架,WebView 都是必不可少的核心组件。

开发人员的接入成本也极低,主要技术就是 Web 开发那一套,前端主要头疼的是各个渲染引擎的适配问题性能问题

现在主流的 JS Engine 是苹果的 JavaScriptCore 和谷歌的 V8,主流的渲染引擎是苹果的 Webkit 和谷歌的 Blink。虽然 W3C 的规范就摆在那里,各个浏览器厂商再根据规范实现浏览器,这也是网页跨端的基础。问题在于浏览器内核实现总有细微差距,部分实现不合规范,部分实现本身就有 Bug,这也是前端摆脱不了适配需求的本质原因。

另一个是性能问题。其实 WebKit 本身的渲染速度还是很快的,但是受限于一些浏览器特性,比如说极其复杂极其动态的 CSS 属性,DOM 树和 CSSOM 的合并,主线程必须挂起等待 JS 的执行,这些都会大大降低性能,前端搞性能优化,一般得依据这些浏览器特性进行减枝处理,但是再怎么优化,在页面性能和交互体验上,和 Native 还是有很大的距离。

2.网页 PLUS:JS Engine + WebKit + Native 能力

直接拿个 URL 扔到 WebView 里是最简单的,其实这样也能解决大部分问题,毕竟前端 90% 的工作都是画 UI 写业务逻辑,但是还有 10% 的功能做不到,比如说要和 Native 同步状态,调用一些系统功能。

要实现客户端和网页双向通讯的话,一般都是借助 JSBridge 进行通信,《JSBridge 的原理》这篇文章总结的不错,感兴趣的同学可以看一下。

JSBridge 只是解决了 Native 和 Web 的互相调用问题,如果我想借助 Native 加强 Web 怎么办?这时候就有了一些探索:

  • 预热:提前创建和初始化 WebView,甚至实现 WebView 容器池,减少 WebView 的启动时间

  • 缓存:把常用的 Web 资源预先存在 Native 本地,然后拦截浏览器网络请求重定向到本地,这样就可以加快 Web 的资源加载速度(也叫“离线包”方案);

  • 劫持:比如说 Web 对网络加载的控制力比较弱,部分有能力的厂商会把所有的网络请求都劫持下来交给 Native 去做,这样做可以更灵活的管理 Web 请求

  • 替换:替换一般指替换 Web 的 Img 标签和 Video 标签,这个最常见的地方就是各大新闻类客户端。因为新闻的动态性和实时性,新闻都是由各个编辑/自媒体通过后台编辑下发的,这时候要利用 Web 强大的排版功能去显示文本内容;但是为了加载速度和观看体验,图片和视频都是 Native 组件替换的

经过上面几步,网页的速度基本可以达到秒开的级别,这里面最典型的就是几大新闻客户端,大家可以上手体验一下。

3.小程序:JS Engine + WebKit

各大小程序平台

小程序,国内的特色架构,本质上是微信成为流量黑洞后,想成为流量分发市场管理和分发自己的流量,所以这是个商业味道很重的框架。

小程序在技术上没什么特别的创新点,本质上就是阉割版的网页,所以微信小程序出来后各个流量寡头都推出了自己的小程序,正如有人吐槽的,小程序的实现方式有 9 种,底层实现多样化,各个厂实现还没有统一的标准,最后就是给开发者喂屎,我也没啥好介绍的,就这样吧。

4.React Native:JS Engine + Native RenderPipeLine

React Native 和 Hermes

React 2013 年发布,两年后 React Native 就发布了,前几种跨段方案基本都是基于浏览器技术的,RN 这个跨段方案的创新性在于它保留了 JS Engine,在渲染引擎这条路上,他没有自己造轮子,而是复用了现有的 Native 渲染管线。

这样做的好处在于,保留 JS Engine,可以最大程度的复用 Web 生态,毕竟 GitHub 上轮子最多的语言就是 JavaScript 了;复用 Native RenderPipeLine,好处在于脱离 WebKit 的历史包袱,相对来说渲染管线更短,性能自然而然就上去了。

那么问题来了,RN 是如何做到跨端的?这个其实全部仰仗于 React 的 vdom。

vdom

前端社区上有些文章讨论 vdom,总会从性能和开发便捷性上切入讲解,从纯 Web 前端的角度看,这些的确是 vdom 的特点,但是这不是 vdom 真正火起来的原因。vdom 更大的价值在于,人们从 vdom 身上看到跨端开发的希望,所以在 React 出现后 React Native 紧跟着出现是一件非常自然的事情。为什么这么说?这个就要先溯源一下 UI 开发的范式。

UI 开发主要有两大范式:Immediate Mode GUI(立即模式)Retained Mode GUI(保留模式)

简单来说,IMGUI 每帧都是全量刷新,主要用在实时性很高的领域(游戏 CAD 等);RMGUI 是最广泛的 UI 范式,每个组件都被封装到一个对象里,便于状态管理和复杂的嵌套布局。无论是网页、iOS、Android 还是 Qt 等桌面开发领域,都是基于 RMGUI 的。这两者的具体细节差异,可以看这篇知乎回答和这个 Youtube 视频

我们再回到 React Native 中,既然 iOS Android 的原生渲染管线都是 RMGUI 范式,那么总是有相似点的,比如说 UI 都是树状嵌套布局,都有事件回调等等。这时候 vdom 的作用就出来了:

vdom 作为一个纯对象,可以清晰的提炼出出布局的嵌套结构,而且这个抽象描述是平台无关的,那么我们就可以利用 JS 生成 vdom,然后将 vdom 映射到 Native 的布局结构上,最终让 Native 渲染视图,以达到跨平台开发的目的。

到这里如果你有些编译原理的知识,你就会发现 vdom 和 IR 有些类似,同样都是抽象于平台的中间态,vdom 上接 React 下接 Native RenderPipeLine,IR 上接编译器前端下接编译器后端,我们只要关心前半段的逻辑处理,脏活累活都让后半部分做。

Hermes

2019 年 Facebook 为了优化 React Native 的性能,直接推出了新的 JS Engine——HermesFB 官方博文介绍了很多的优点,我个人认为最大的亮点是加入 AOT,传统的 JS 加工加载流程是这样的:

Babel 语法转换Minify 代码压缩install 下载代码Parse 转为 ASTCompile 编译Execute 执行

Hermes 加入 AOT 后,BabelMinifyParseCompile 这些流程全部都在开发者电脑上完成,直接下发字节码让 Hermes 运行就行。

Bytecode precompilation with Hermes

这样做的好处在于,可以大大缩短 JS 的编译时间,不信的话大家可以用 Chrome 分析几个大型网站,JS 的解析加载时间基本占时都是 50% 以上,部分重型网站可能占时 90%,这对桌面应用来说还好,对于电量和 CPU 都要弱上一线的移动平台来说,这些都是妥妥的性能杀手,Hermes 的加入可以大大改善这一情况。

目前 React Native 0.64 也支持 iOS 使用 Hermes 了,如果有做 RN 业务的同学可以玩一玩,看看在 iOS 上的性能提升有多大。

5.Flutter: Dart VM + Flutter RnderPipeLine

Flutter 和 Dart

Flutter 是最近比较火的一个跨端方案,也有不少人认为这是最终的跨端方案,毕竟桌面软件时代,最终胜出跨端方案就是 Qt,他们的共同特点就是自带了一套渲染引擎,可以抹平终端差异。

Flutter 的创造还是很有意思的,这里有个 Eric 的访谈,视频中说 Eric 差不多有十几年的 Web 渲染领域工作经验,有一次在 Chrome 内部他们做了个实验,把一些乱七八糟的 Web 规范去掉后,一些基准测试甚至能快 20 倍,因此 Google 内部开始立项,Flutter 的出现了。至于 Flutter 选择 Dart 的理由,坊间一直传说 Flutter 开发组隔壁就是 Dart 开发组,离得近就好 PY 交易,反正 Dart 也没人用,没啥历史包袱,可以很好的相应 Flutter 的需求。

Flutter 的架构也是比较清晰的:

  • 虚拟机用的 Dart VM,Dart 同时支持 JIT 和 AOT,可以同时保证开发效率和运行效率
  • 渲染引擎先把 Dart 构建的视图数据传递给 Skia,然后 Skia 加工数据交给 OpenGL/Metal 这两个图形 API,最终交给 GPU 渲染,整体上比 WebKit 的渲染流水线清晰不少

从纯粹程度上看,Flutter 是做的最彻底的,虚拟机和渲染引擎都没有用业内的成熟方案,而是自造了一套,好处就是没啥适配压力,坏处就是太新了,业务开发时往往会遇到无轮子可用的尴尬状态,如果谷歌大力推广,国内大厂持续跟进,前景还是很光明的。

6.其它方向的探索:JS Engine + Flutter RnderPipeLine?

社区里有一种声音,认为 Flutter 最大的败笔就是不能用 JavaScript 开发。这时候就会有人想,如果我们把 Web 技术和 Flutter 技术结合起来,用 JS Engine 对接世界上最大最活跃的 JS 社区,用 Flutter 渲染引擎对接高性能渲染体验,国安民乐,岂不美哉?

岂不美哉?

目前来说一些大厂还是做了一些探索,我看了一些分析和项目架构,感觉就是做了个低配版的 React Native,React Native 的现有架构有一个性能瓶颈就是跨语言调用成本比较高,而这些大厂的调用链路多达 4 步:JS -> C++ -> Dart -> C++,更加丧心病狂,目前看无论是上手和推广都是没有直接用 RN or Flutter 方便。

三、各跨端方案的不足之处

跨端方案不可能只有好处的,各个方案的坏处也是很明显的,我下面简单列一下:

  • 网页:性能是个过去不的坎儿,而且 Apple 明确指出不欢迎 WebView 套壳 APP,有拒审危险
  • 网页 PLUS:技术投入很高,基本只能大厂玩转
  • 小程序:对开发者不友好,技术半衰期极短
  • React Native:基本只能画 UI,一旦做深了,只会 JS 根本解决不了问题,Java OC 都得学,对开发者要求比较高
  • Flutter:Android 支持很好,但 iOS 平台的交互割裂感还是很强的,而且和 RN 问题一样,一旦做深了,必须学习客户端开发知识,对开发者要求比较高

总的来说,在牺牲一定用户体验的前提下,跨端方案可以提高开发者的开发效率和公司的运行效率,我个人认为,只要某个方案的 ROI 比较高,其实是还是可以投入到生产的。

四、总结

本文到此就结束了,我把各个跨端技术提炼为为虚拟机和渲染引擎技术,然后以这两个核心技术的角度去拆解各个跨端方案。一旦概念理清,在面对性能调优等技术场景时,就能抓住主要矛盾,更快更好的发现问题,解决问题。


个人微信:egg_labs





一个小尾巴

欢迎关注公众号:卤代烃实验室:专注于前端技术、混合开发、图形学领域,只写有深度的技术文章