前端早早聊大会-与掘金联合举办-全年学习

一、前言

image.png

大家好,我是阿里巴巴的晟怀,目前在阿里巴巴淘系技术部做 Flutter 以及 Rax 相关的事情。今天我分享的主题是如何使用 Rax 构建 Flutter 应用。

Flutter 大家应该很熟悉了,谷歌出品的一个新的跨端框架,让开发者可以用 Dart 的语言去构建 iOS 和 Android 的应用程序。

Rax 是阿里巴巴推出的一个面向多端的前端框架,它的写法非常像 React,几乎是一致的。目前 Rax 支持 H5、Weex、微信小程序以及支付宝小程序等各个平台。也就是说,使用一套 Rax 代码就可以在多个平台运行,而且会保持渲染结果一致。

今天分享的主要内容是,我们如何通过 Flutter 所提供的底层渲染跨平台支持,在上层构建一套兼容 W3C 规范的渲染 API,打通 Rax 应用生态,最终做到和浏览器的渲染一致。也就是说,你可以使用一套 Rax 代码,在 H5 上运行,也可以在 Flutter 上运行。

二、自我介绍

image.png

先自我介绍一下,我 2016 年到 2019 年在百度工作,2019 年之后在淘系终端架构,入职淘系以后就开始做 Kraken 相关的事情。上图的左边是我的 Github。

三、渲染技术的对比与可行性

image.png

先做一个简单的对比,从渲染角度来讲,渲染技术的本质就是画 UI。通过程序把你的图案画到屏幕上面,使用代码来画框。

最左侧的安卓是用 Java,加上 Layout XML 去画这个 UI。它的底层跟 Weex 和 React Native 是类似的,因为 Weex 和 React Native 都是调的系统内置 UI,只是在上一层有不同的前端框架 DSL 以及它所实现的 VDOM。客户端左边三个底层都是使用 Native View Tree 和 Layout Tree 这样的一套客户端自有的渲染结构来进行绘制。

右侧是 Flutter 和 Chrome,它们俩在架构上其实是很像的。Chrome 上采用 JS 作为编程语言进行开发,Flutter 是使用 Dart 语言去写一些业务相关的代码。Chrome 主要使用 DOM Tree 来进行布局的抽象。底层对应的是 Layout Tree 和 Layer Tree 这些针对于不同阶段渲染所需要的一些树状结构。Flutter 所对应是 RanderObject Tree 和 Layer Tree。这所有的渲染结构中,在最底层是使用 Skia 这样的图形渲染引擎,通过 Skia 把图像数据传输到 GPU,然后通过显卡把图像输出出来。

从这样一个从底向上结构来看,实现跨端应用架构上是可行的。因为它最底层都是使用 Skia,区别是上层所使用的一些技术方案不同。

四、生态对比

我们可以再做一些更细致的对比,从生态角度上去对比它的好坏。

image.png

4.1 Flutter

最左边的 Flutter,Dart 语言就不说了,估计大部分的前端开发者都不会。

但它在其它方面有比较好的一些优势:

  • Flutter 支持在开发的时候 JIT,而在编译的时候 AOT。为什么会有 AOT 呢?因为像 Apple 这样的特殊生态,它是不支持第三方在应用中使用 JIT 这样的技术的。比如 V8 引擎就跑不了,这是因为它受苹果的安全限制,所以我们需要 AOT 这种预先编译的方案,这样代码才能够在 Apple 设备上高效运行。
  • Flutter 的底层是很像浏览器的,不存在多端不一致的问题。完全是 write once,run anywhere。它的性能也是相当好,因为它是一个简化的浏览器渲染管线。
  • Flutter 的生态还是比较完善的,社区已经基本上建设起来了,同时它内置的一些动画看着也还不错。

4.2 React Native/Weex

对于 React Native 和 Weex,它俩可以一起讲。React Native 跟 Weex 都使用了 JS 引擎。

这地方有个问题,它们的 JS 引擎都是使用 JavaScriptCore:

  • 在 iOS 的情况下,它是属于关闭 JIT 的模式;
  • 在 Android 上面确实是有 JIT 的。

但不论是 Weex,还是 React Native,它们都有一个最致命的弊端,就是使用了原生的 view 去做渲染,因此总会出现在双端有那么一点点不太一致的情况。虽然声称说是跨终端,但是当真正使用的时候,发现还是有各种各样的区别。Weex 的话,它的一些生态其实相比 React Native 还更差一些,不过性能倒是能稍微好一点点。

4.3 浏览器

最后对于我们最熟悉的浏览器来说,它生态以及它的开发效率都是最高的,内置的功能都很好。大家所诟病的就是它的性能不是特别好,以至于在手机上使用的时候,H5 往往都是降级策略,而不是首选的方式。对于像手机的操作体验也不是特别好。

五、为何选择 Flutter

image.png

5.1 真跨端的契机

为什么我们会选择 Flutter 呢?从前面的介绍来说,其实 Flutter 就像浏览器一样,它是从底向上,完全是自己画上去的。是从最底层的渲染架构到最后生成 UI,像 Web 一样拥有完全的跨终端渲染技术。同时它也抛弃了很多浏览器所带有的历史包袱,这也是为什么我们发现了在客户端能够真正做到真跨端渲染技术的一个契机。

5.2 前端工程师的视角

image.png

其实 Flutter 终究是一个面向客户端的技术栈,因为它的编程语言是 Dart,不是 JS。对于前端开发来说,还有一些困扰的,比如前端习以为常热更新,这个能力客户端是没有的。因为 Flutter 应用在生产环境是采用 AOT 模式进行编译的,所以也就无法具备动态刷新的机制。因此 Flutter 所开发出来的应用和常规使用客户端技术所开发出来的应用是一样的,只能通过发版方式进行下发。

从前端工程师的角度来说,前端工程师想去使用 Flutter 会有很多的阻碍,包括你可能不太熟悉 Dart,它的布局方式是使用的是 Widget,而不是使用 CSS 这种方式。关于代码,如果你看过一些 Widget 的代码,你会发现它比 JSX 更不容易读,同时它的调试工具也不是前端所熟悉的 Chrome Develop Tools,因此,问题还是不少的。

5.3 刚性诉求

image.png

不过在前端和客户端领域,尤其是在手机应用上面都有一些比较刚性的需求:

  • 动态性,万一写出 bug 了,你要能够快速修复;
  • 前端的生态,它远远大于客户端的生态,毕竟浏览器的布局技术是要强于客户端的,因此会有相当多的生态支持,包括很多的开发者,各种各样的库。

因此,Rax 团队,就是希望能够在 Flutter 这样一个平台上去构建动态性和可连接的前端生态这两个能力。

image.png

这里想要这样引用 Jeff Atwood 的一对句话,“任何可以使用 JavaScript 来编写的应用,最终都会由 JavaScript 编写”。包括最近的 SpaceX 的载人飞船,它的图形界面也是使用 Chrome 渲染的,JavaScript 将会越来越强大。

六、Kraken

image.png

接下来我们要介绍 Rax 团队所新推出的一个新项目:Kraken。

image.png

Kraken 想要达成的目标是,像这样一个前端很熟悉的代码,我们是不是可以直接运行。

我们可以来演示一下 Kraken 究竟是什么样子的。

image.png

演示中…具体可参考录播。

image.png

因为对接的是前端生态,也就是说任何前端的项目都应该是可以运行的。

  • 例如前端库 Mobx、Redux…
  • 例如编译工具 Webpack、Babel…
  • 例如编程语言,TypeScript…

image.png

不过像刚才所使用的 Rax 工具链,都已经内置这些工具的支持了。只要最终能够被编译成 JS,那么它就可以被 Kraken 所运行。这次分享我会先简单介绍一下 Kraken 背后的原理,后续我们做的比较完善之后,会考虑开源。

6.1 Kraken 的机制

image.png

我先介绍一下它的主要机制,在整个架构的最上层是 Rax 框架,当然也可以被换成像 Vue 或者 React 等其他的前端框架。

Kraken 的最上层是一个基于 W3C 标准而构建的 DOM API,在下层是所依赖的 JS 引擎,我们通过 C++ 构建一个 Bridge 与 Dart 通信。然后我们这个 C++ Bridge 把 JS 所调用的一些信息,转发到 Dart 层。Dart 层通过接收这些信息,会去调用 Flutter 所提供的一些渲染能力来进行渲染。

如果说 Kraken 跟 Flutter 之间的关系的话,它和常规的 Flutter 应用还是有一些区别的。Kraken 所使用的 Flutter Engine 和 Flutter 应用是完全一致的,我们没有做任何修改。不过在 Dart 层我们倒是做了很多工作。

Kraken 是不依赖 Flutter Widget,而是依赖 Flutter Widget 的底层渲染数据结构 —— RenderObject。Kraken 实现了很多 CSS 相关的能力和一些自定义的 RenderObject,直接将生成的 RenderObject 挂载在 Flutter RenderView 上来进行渲染,通过这样的方式能够做到非常高效的渲染性能。

image.png

这个是 Kraken DOM 模型的一个抽象,它提供了和浏览器一样的 DOM API,并暴露在 JS 环境中,JS 通过使用 DOM API 来创建一个 DOM 树,就像左边那个图一样。

image.png

在创建 DOM 树的过程中,它会有一个通信的过程,我们将所有 API 的调用都转化成一段数据,然后发送给 Dart 层进行处理。比如像右边这样的一个 JSON 字符串,JSON 字符串会传递到 Dart 那边进行解析,进而去构建新的 UI 界面。

image.png

DOM API 发送的消息传递到下层之后,我们究竟是是做了什么改造才能够让它在 Flutter 上运行呢?

我们首先做了一些抽象,比如 Elements 抽象,Elements 的抽象跟浏览器的抽象是很类似的,抽象出 div span 这些跟浏览器一样的标签。然后我们通过抽象标签之后,它就会生成一个 element 树,通过 element 树,我们就可以控制它的下一层 — RenderObject 树。RenderObject 真正的描述了渲染过程中的很多细节,像每个框的大小尺寸,位置坐标,像 CSS 属性在这里就会被计算并转化为每个框的尺寸和坐标。

不管是 Kraken 还是浏览器,它在进行 CSS 布局的时候,都是在 Rendering 这个阶段去实现的。在这个阶段会确定每一个框的大小和位置。因此在这个时候,我们会有一些流式布局的计算和 Flexbox 的计算,还有定位能力的计算。当这些框的位置和大小都算好了,下一步就是生成图像数据,然后发送到 GPU 去画出来。

image.png

除了布局相关的能力,我们还做了一些跟浏览器一样的 API。例如 setTimeout 还有 setInterval,以及常用的 DOM API,还有一些 Window API,像 location,open,close,还包括网络请求,我们也做了 Fetch、WebSocket 这些兼容了 W3C 标准的 API。

image.png

对于前端调试,我们也把调试工具接入到了 Chrome Develop Tools。通过 Bridge 把 Chrome 的调试工具一一进行了对接,即使底层是 JSC 的引擎,也可以把它直接接入到 Chrome 工具上面,然后就可以获得像在像浏览器一样的调试体验。

6.2 Kraken 的演进

image.png

接下来就是 Kraken 未来持续演进的事情。

image.png

刚开始也介绍了,其实 Rax 它是目前 Kraken 跨端所支持的第一个前端框架。但在架构层面,我们的设计是可以支持多种框架的,不只是支持 Rax 框架,也可以支持 React、Vue、Angular 这些框架,因为这些框架其实底层都是使用了 DOM API,因此在不需要多加改动情况下都是可以跑起来的。

6.3 Kraken 与 WebView

image.png

为什么有了 WebView 这样的一个渲染器,我们还要做 Kraken?

因为我们最终希望是在某些特定领域,我们能够做得比 WebView 更好,WebView 是一个非常强大的渲染引擎,但由于众所周知的原因,它在手机上面的体验,其实不是特别好。因此这就给了 Kraken 一个契机,比如说在手机上某些体验可以做的比 WebView 更好。

image.png

第一个问题是我们是不是需要 WebView 的所有渲染能力

拿选择器作为例子。选择器虽然是非常方便的一个技术,但是在有了前端框架之后,尤其是 CSS in JS 这个方案的出现之后,很多的工程师其实不需要使用选择器就可以实现页面。选择器突然一下就成了一个相对比较过时的技术。但因为选择器比较方便,还是会有人选择使用选择器,它方便的同时就会带来一些性能问题,其中包含大量的计算过程。对于手机这样的一个性能比较紧缺的环境,我们希望尽量不使用选择器,或者干脆不支持使用选择器,或者选择性支持

image.png

如果我们从一个渲染机制的角度去分析的话,其实 WebView 设计更多️的是向 PC 靠拢,对于手机也不算太合适:

  • 第一个地方, HTML、CSS。WebView 加载页面,它起点是从 HTML 开始的,通过 HTML 再开始加载 CSS 和 JavaScript。但是现在使用前端框架,你往往根本就不需要有 HTML,只需要一个 JS 就好了。但是现有的浏览器限制你必须要先从 HTML CSS 开始,然后再开始加载 JavaScript。
  • 第二个地方,刚才前面也提到了,浏览器有相当多向后兼容的一些布局逻辑,这样逻辑是隐藏在它的 Layout 实现层面,很多布局代码其实会降低它的运行效率。

第二个问题是渲染的光栅化

浏览器它的架构是属于异步光栅化的。异步光栅化怎么解释呢?用一句话来说,就是把 CPU 生成的绘制数据缓存在一个地方,然后过一段时间 GPU 再过来拿,最后把它画出来。如果说在某个时段你 CPU 生成的数据特别多,这时候 GPU 可能还来不及消耗这么多的数据,比如说你疯狂往下滚,滚的时候有很多列表,这个时候 CPU 就疯狂生成图案,但是这时候 GPU 就根本来不及把它们都画出来。此时内存就会暴涨,然后应用就卡了。这就是特别典型的因为异步光栅化架构所导致的一个问题。

image.png

然后除了浏览器之外,其他的像手机 APP Native 应用都是采用使用同步光栅化。包括 Flutter,同步光栅化这个过程,就是 CPU 在生成渲染数据之后,会同步去调用 GPU 然后再画出来。这中间的过程是阻塞的,CPU 在 GPU 没有画完之前无法进行下一步操作。因此就没有一个缓存来吃内存的占用,更适合于向客户端这样一个资源紧缺的场景。并且 Kraken 它是摒弃了 HTML 和 CSS 这些东西,它只支持 JavaScript 来构建布局,非常契合现在前端框架的一种思路。

同时 Kraken 的 Layout 相比浏览器会更加的轻量化。准确来说是我们把一些在客户端上,把并不常用的渲染能力给它去掉,然后它的一些逻辑就会更加简洁。同时有一些很复杂的功能,但是又没什么用的,我们干脆不做它,这样的话在架构设计上也会有很多优势。

image.png

6.4 Kraken 与云

Kraken 是一个面向客户端的渲染引擎,但是它跟云有什么联系呢?

image.png

大家应该都听过云游戏这样的概念。云游戏其实就是把游戏的计算过程放置在服务器上运行,然后用户只需要使用一个像手机这样的一个移动设备,连到服务器上就可以玩游戏了。它的架构其实是让服务器去承担计算,然后手机去负责输出。

这跟现有常规的方案是不太一样的,现有方案是在同一个设备上完成计算与输出。

云游戏能带来什么好处呢?它可以在性能较低的设备上,玩到需要高性能计算才能玩的游戏。这就是它的一个优势。

image.png

一些国外的企业,比如微软的 Xbox、Google 以及 Sony,他们都有类似这样的业务。但是这些业务,实际还是有一定的性能体验的问题,还不是特别完美。但是如果网络逐步发展,假如延迟降到足够低,比如说在一毫秒以内,延迟的问题得到解决,那么云游戏就会变成一个未来的趋势。

image.png

Kraken 也有类似的技术潜力。我们在内部进行了一些实验,成功实现了通过 Linux 服务器来进行绘制这样的功能。一些典型应用场景,比如 IOT 的应用场景,这些设备的性能是相当的低的,但是依然可以画出一些很高级的 UI。

image.png

按照现在的一些技术趋势,所有能够被云化的应用,它其实最终都将被云化。只不过目前被搬到云上的,很多都是一些离线的、没有界面的、服务端的东西。但是未来,像 UI 的绘制,也是可以被放在云上面的。

image.png

再介绍一下 Kraken 在云端简单的技术架构。它主要分为左侧的客户端,右侧的云端。它们之间连接使用的是 Websocket、WebRTC 这两个通信的机制。

右侧是 Kraken Engine 负责渲染。它渲染出来的结果,并不会输出到服务器的显示器上,而是被输出到一个虚拟显示器上,叫 Screen Capture。然后虚拟的显示器就会把 Kraken 所渲染出来的内容,通过 h265 编码来生成一个视频。然后可以通过放视频的方式去查看 Kraken 的渲染结果。不过 UI 跟视频间的最大差异在于是否可以交互,所以还需要在视频上去捕获用户交互操作,比如触摸操作、点击事件。再通过 WebSocket 方式发送到 Kraken 的 Render Engine。

image.png

云端一体化是我们预测未来的渲染技术。渲染最终的一个形态,可能并不仅仅是在一个设备上,也可能会是一个集群。大家所使用的设备就是都是一个显示器,而不是一个真正可以外置的设备。

6.5 总结

image.png

最后做一些技术总结,像 Kraken 这样的一个技术项目,它真正给前端工程师带来什么?

按照我的理解,Kraken 具备重新定义浏览器的潜力。我们做 Kraken 实际上是重新把 CSS 这样的渲染能力给做一遍,Weex 跟 RN 也是一个同样的做法。但是 Weex 和 RN 完成了布局能力之后,还会受限制于双端不一致的情况。因此它们并不能完全放开,只能够尽可能去接近于双端原生带来的这些东西。

但是 Flutter 就不是这样,Flutter 可以自己控制所有的东西。Kraken 面对的其实是一个像 Canvas 这样的画布,因此有一些更高级的能力,其实是可以做到更彻底。

image.png

第一点,比如现有的前端 UI 库,如果稍微了解前端开发跟客户端开发,其实会发现一个最典型的差异就是,前端工程师使用 UI 就会去引用一个用 HTML CSS 写的包,浏览器它默认不提供 UI。但是如果我们去开发客户端的话,你会发现客户端把 UI 都给你写好了,你只需要调用苹果或者安卓给你提供的 API,你就可以画出系统内置 UI 所提供的样式,这就是一个典型差异。浏览器使用 UI 是需要自己去加个包的,但像客户端是不需要的。

单独去加载一个 UI 包,肯定会带来很大的弊端。应用体积大,加载慢,有很多的 CSS 文件都需要在浏览器去渲染。客户端为什么那么快,因为它的 UI 都是预先被加载好的。因此在这个时候,如果说我们用 Kraken,一个像浏览器一样的开发方式,我们仍然可以把 UI 组件库做到 Kraken 里。当然这个 UI 组件库肯定是针对你公司的业务进行定制的,应该来说每个公司它都不一样,但是对于单个公司而言,它的 UI 样式肯定是统一的,因此组件库是可以被内置到公司内 Kraken 渲染引擎中的。这样的话,它就能带来相比任何前端现有的缓存方案以及各种预加载方式都要好的使用体验。到时候只需要写一个标签,它直接就渲染出来了,根本没有任何等待的时间。

第二点,有关像 CSS3 等动画这种能力,虽然说浏览器提供了 transform、translation 这些动画能力,但是其实很多时候是不够用的。我们更多的时候还是会引一个图形库去做一些支持,这些图形库其实都是很重的,都是比较庞大的一些东西。这些图形库在加载的时候,也会占用相当多的加载资源,然后让应用的编译体积变大,少则几百 k,动则好几 M。这些常用的动画库也是可以被集成到 Kraken 里的,这个浏览器是绝对做不到的,所以也可以去吊打现有的任何一个预加载方案。

第三点,现有的前端工程化方案。现在工程化其实会做各种各样的优化方式,比如说缓存、分包以及一些图片压缩,或者是打一些 Polyfill。但是有了像 Kraken 这样的东西的话,很多时候可能并不需要这种那种特别极致的优化方式。因为我们会把这些优化方式给转移到应用所跑的客户端里面,而不是应用外边。这个思路,其实就是客户端的一个思路,不是浏览器的一个思路,毕竟在 C 端领域,现在大家的 H5 开发,很多时候应该还是专注于面向客户端端内的一个场景。

七、联系我们

image.png

这个二维码是淘系前端技术部公众号的二维码,大家可以扫码关注一下。我们这边也在招跟跨端渲染相关的一些工程师,如果有兴趣的话,也可以通过钉钉方式去联系我。

八、荐书

image.png

我主要还是要推荐一些跟健康相关的书。平时写代码还是比较累的,我相信很多人应该也会跟我有一样的感受。因此需要学会一种技巧,能够摆脱疲惫感,科学管理你的疲惫和压力,这样才可以做到更高效率地工作。谢谢!


别忘了点下方送稻谷