深入解析基于 Flutter 的 Web 渲染引擎「北海 Kraken 」技术原理-阿里云开发者社区

开发者社区> 开发者小助手> 正文

深入解析基于 Flutter 的 Web 渲染引擎「北海 Kraken 」技术原理

简介: 大家好,我是染陌,这是我在 全球开源技术峰会 GOTC 上的一个 topic ——《基于 Flutter 的 Web 渲染引擎「北海 Kraken」》。我主要从技术角度来分享 Kraken 的一些实现原理以及关键的技术特性,现在整理成文字版分享给大家。
+关注继续查看

作者:染陌 Alibaba F2E  8月23日


image.png

大家好,我是染陌,这是我在 全球开源技术峰会 GOTC 上的一个 topic ——《基于 Flutter 的 Web 渲染引擎「北海 Kraken」》。我主要从技术角度来分享 Kraken 的一些实现原理以及关键的技术特性,现在整理成文字版分享给大家。

Kraken Github:https://github.com/openkraken/kraken

Kraken 官网:https://openkraken.com/



北海的技术背景



说到北海的技术背景就不得不提及跨端技术的演进,很多同学应该都比较熟悉跨端技术的历程了,我还是简单讲一些。


我们知道,浏览器是最成熟的天然跨平台方案。早在 PC 时代,浏览器已经成为了互联网的入口,大家都会习惯性通过浏览器来进行网页的浏览以汲取各种信息,当时我们把这种上网的方式叫做“冲浪”。然而到了移动时代,浏览器在移动设备上并没有一个抢眼的表现,反之因为内存大、弱网环境白屏久、传感器能力缺失(标准跟进慢)等问题使各种质疑不绝。


为了弥补上述浏览器在移动端的一些不足,出现了 Hybrid 技术,在 Web 之上通过容器的能力实现一些非标准化的超集,同时也通过 prefetch、离线包等各种技术来提升首屏的加载性能。


此后,出现了类 RN 的方案(典型代表 React Native),它的原理是通过 JS engine 将 Native 控件与前端生态实现一个桥接,通过 Web 开发业务逻辑提升效率,而向下通过 Native 控件渲染来提升性能及体验。但是这类方案的缺点是无法完全抹平两端的差异,没有解决一致性的问题,而最终将复杂度暴露给了开发者。


image.png


Flutter 作为跨端届的新宠,这两年也获得了越来越多的关注,下面介绍一下 Flutter。


image.png


Flutter 的优点是性能好、由于其通过自绘渲染使得跨端一致性高,但是它也有它自身的缺点,比如生态自成一派,既不是前端也不是 Android/iOS。


这就是引出了一系列的问题。


  • 首先,前端(JavaScript)或客户端(Swift / JAVA)转型都有一定成本,但是由于端侧的 GUI 体系大同小异。笔者站在一个前端开发者的视角去看语言上的学习成本并不会特别高,有 React 或者 Vue 等前端框架经验的同学可以通过简答的学习快速上手。对于一些小型的创业团队,确实可以小步快跑快速学习上手并开发,但当组织庞大到一定程度,这个转换的成本将会指数级上升。
  • 其次,生态圈等待重新建设,一些 Flutter 开发者朋友或许觉得目前 Flutter 开发已经有挺多的 pub 可以直接使用了。但实际上生态圈不止于 Flutter pub,还有各种已有的基础链路,比如建设相关的 CI/CD,再比如搭建等等。这一系列的生态都需要重新建设,成本是非常大的。
  • 再次,已有的非常多业务都是通过 JavaScript + 前端框架开发的前端项目,我们如果想把它们迁移成 Dart + Widget 成本无疑是非常庞大的。


在面对如此多的问题以及切换的高成本的同时,我们也期望通过 Flutter 给我们的业务带来更多的技术的可能性,同时改善 Web 容器在端上的一些性能及体验问题。那么,引入一项新技术的第一步是解决引入这项新技术的成本问题,所以我们积极探索一种将前端生态与 Flutter 结合起来的方案。


image.png


于是产生了本次 topic 的主角——北海 Kraken。


Kraken 是一款高性能 Web 标准的自绘渲染引擎,具有高性能、易扩展、基于 Flutter 以及 遵守 Web 标准的特点。


image.png


下面我列举了一些北海在阿里的一些应用场景,在 C 端 APP 或者 IoT 设备上,北海都有相关的落地。


image.png



北海的技术原理



在介绍 Kraken 的技术原理之前,我先演示一下如何开发一个 Kraken 应用。因为 Kraken 是基于 W3C 标准来开发的 Web 渲染引擎,所以上层是框架无关的,无论开发者使用的是 Vue 或者 React 还是 Rax 都可以在 Kraken 上进行一个应用开发。


以 Vue.js 开发为例,下面是我用 Vue 官方提供的 vue-cli 起的一个项目。具体的代码见官方示例


可以看到的是,最左边是 Vue 的相关代码,右边分别是该应用在 Chrome(左)上跑的结果以及在 Kraken(右)上跑的结果,大家可以看到结果是完全一致的。


image.png


了解了如何开发一个 Kraken 应用 ,我们再来理解一下 Kraken 的技术原理。为了大家更好地理解,首先我们来比较一下 Flutter 于 Webview 的渲染流程。


WebView 的渲染流程相信大家非常熟悉了,面试中非常经典的题目就是一个 URL 输入如何最终渲染到屏幕上了。总的来说就是解析 HTML、JS 以及 CSS 文件,执行相应 JS 调用 DOM API,最终会生成 DOM Tree 以及 CSSOM Tree,然后会计算最终得到 Render Tree,经过 Layout 以及 Paint 流程生成一系列的 Layer,最终通过合成以及光栅化渲染到屏幕上。


再看 Flutter 这边,Flutter 经典的三棵树——Widget Tree、Element Tree 以及 RenderObject Tree。Widget Tree 对应到前端类似于前端框架这层,而 Element 与 DOM Tree,RenderObject Tree 与 Render Tree 分别对应,最终也会通过 Layout 以及 Paint 一系列计算生成 Layer,然后通过合成以及光栅化渲染到屏幕上。


image.png


那么,我们再将前端框架加入到我们整个流程中进行一个更加直观的对比,这里还是以 Vue.js 为例。


Vue.js 会在运行时生成一系列 Vdom 产生 Vdom Tree,再通过 platfom 的抽象调用具体平台的 API。


image.png


那么我们就会发现,只需要把我用红框圈出来的部分的流程进行互换,就可以实现我们最终想要实现的效果(上层 Web 开发,下层基于 Flutter 进行渲染)。


image.png


基于以上设想,那么北海的渲染流程就出来了。


目前主流的前端框架都会将产物打成一个 JS Bundle,通过标准的 DOM API 去操作具体的视图,而 HTML 内一般只有一个根结点。在 Web 下,页面会先请求 HTML 文件,再解析 Script 标签去加载对应的 JS 文件。而 Kraken 的入口设计成了一个 JS 文件,这样做可以减少一次请求,加快首屏的渲染。


该 JS 文件会在 JS Engine 中执行,Kraken 的 runtime 通过 JS Engine Binding 的方式提供了一系列 Web 标准的 API 接口,调用相应 API 会执行相关逻辑并创建一系列需要发送给 Dart 层处理的指令,指令通过一个 struct 进行存储。C++ 通过 FFI 将相应的指令底层的 address 发送到 Dart 这边,Dart 处理相关指令并生成 Dom Tree。同样的,CSS 也会通过 Parser 生成对应的 CSSOM Tree,最终会结合生成 Flutter 的 RenderObject Tree,经过 Layout 以及 Paint 的一系列计算,生成对应的一系列 Layter,然后通过合成光栅化最终上屏显示。


同样的,在最新的实现中,我们考虑到了 SSR 应用的场景,所以加入了 HTML 为入口的北海应用开发方式,通过 HTML Parser 即可解析对应的 HTML 文件,后续流程是一样的。SSR 的支持也让首屏的秒开率更上一层楼。


image.png


那么了解了 Kraken 的整个渲染流程,那么我们如何基于 Flutter 去完成 Web 标准的渲染引擎的开发呢?


image.png


那么要基于 Flutter 去做这个事情,就必须先了解 Flutter 的架构。


Flutter 最上层是 Dart 实现的 Framework,包含了响应式框架、官网组件库以及实现布局与绘制协议的部分。中间是 C++ 实现的 Flutter Engine,他是渲染流程的下半部分,提供了一些基础能力,以及将 layer 合成以及光栅化后输出。最下层的 Embedder 层,则负责具体 platform 的一些实现,以实现跨平台。


image.png


不难发现,最 Dart Framework 的 Widget 是对 UI 的抽象,实现了一套响应式框架,对应到前端就是 Vue / React 等前端框架。而下方的布局协议,可以对应 W3C 的标准来实现一套基于前端标准的布局与绘制协议。


image.png


那么我们就可以得出北海的架构设计。


先看左边,左边还是上面介绍的 Flutter 的整体架构,Flutter 的 Widget 能力可以通过插件的形式注册到 Kraken 中去,成为一个前端标准的 Tag,JS 可以动态化地调用及控制渲染。整个左侧的 Flutter 架构支撑了上层的 Flutter 生态,使 Flutter 生态也可以通过插件的形式融合到整个 Kraken 的渲染体系中去。


右边是 Kraken 的架构实现,Kraken 的实现并没有把实现侵入到 Flutter Engine 中去。在 Dart 层,通过实现 W3C 标准的一系列布局与渲染能力,为上层提供了一些列标准化的能力,比如 Element、CSS、以及各种 Web 标准的 Module 等。在上层 Kraken 的 runtime 通过 JS Engine Binding 的方式提供了一系列 Web 标准的 API 接口,调用相应 API 会执行相关逻辑并创建一系列需要发送给 Dart 层处理的指令,指令通过一个 struct 进行存储。C++ 通过 FFI 将相应的指令底层的 address 发送到 Dart 这边,最终 Dart 根据指令调用前面说的标准化能力,以完成对接。通过该实现,为上层的前端生态提供了支撑,凭借丰富的前端生态,开发者可以享受前端生态带来的高效的开发体验。


image.png



关键技术特性



首屏的加载性能是一个 C 端场景的关键指标,长时间的白屏会极大地影响用户体验。


Kraken 在 首屏初始化时需要创建大量的节点,大量的时间耗费在通信上,所以优化首屏性能迫在眉睫。


image.png


在上面技术原理部分我们知道,Kraken 需要通过 Bridge 来完成 C++(JS Engine) 与 Dart 之间的通信,以达到将指令传递到 Dart 层的目的,Bridge 的架构也进行了三个版本的演进。


最初的第一代方案,我们侵入了 Flutter Engine,使数据从 JS Engine 传递到 Flutter Engine 中,然后通过 native bingding 最终将数据发送给 Dart 层。这一代的方案非常明显的缺点是侵入了 Flutter Engine,开发时需要编译 Flutter Engine 需要耗费大量的时间。同时,对于 Kraken 的架构来说,侵入 Flutter Engine 也并不是一个合理的设计。


后来出现了 Dart FFI,可以实现 C++ 与 Dart 之间的高效通信,所以产生了第二代方案。第二代 Bridge 方案通过将 JSON 数据序列化后,通过 Dart FFI 将数据传递到 Dart 层,Dart 层再通过 JSON 的反序列化以拿到最终的数据。这代方案比起上一代方案可以解决侵入 Flutter Engine 的缺点,但是引入了字符串的拷贝以及 JSON 序列化反序列化的时间长的问题。


image.png


为了解决上述问题,于是产生了第三代 Bridge 方案。第三代 Bridge 方案通过共享内存的方式定义了一个标准的 40 Bytes 的 Struct 来存储指令,而通过 Dart FFI 传递的只是指令的地址,C++ 跟 Dart 两边都依赖地址来访问相关数据。这样做解决了 JSON 序列化反序列化的问题,节约了时间,并且少一次数据拷贝。同时,由于内存是 40 Bytes 对其的,可以提高内存的访问效率。


image.png


下面是一些实际线上页面带来的首屏收益。


image.png


无限滚动的长列表是困扰前端开发者很久的历史性问题了,大量的 layout 导致页面卡顿,以及滚动时 Paint 的时间长导致滚动掉帧,页面的体验非常糟糕。社区也有非常多的前端的解决方案来处理该问题,而在 Kraken 上,我们也期望在容器层解决该问题。


image.png


在 Android 跟 iOS 上也分别有 RecyclerView 以及 TableView 来解决该问题,他们的原理分别是在可视区域 viewport 外定义一块缓冲区域,当节点超过该区域时进行动态释放,进入该区域时动态创建,以及通过一系列节点进行属性替换的方式来保证节点数不爆炸。Flutter 中也提供了类似实现 Sliver,那么我们能否用 Sliver 赋能前端解决该问题呢?


image.png


Kraken 定义了一个新的 display 属性 sliver,通过将节点的 display 属性设置为 sliver,则可以直接使用 Flutter 的 Sliver 能力,以达到节点超出可视及缓存区域后动态回收的一个能力。可以看到我们使用 1000 个卡片的 DEMO 进行测试,sliver 下比起 block 有明显的收益。


同时,该标准也已经在 W3C 中文兴趣小组 进行了讨论,期望在大家讨论充分以及达成共识后,尝试将此提案向 W3C 进行提交,反哺前端社区。


image.png


image.png


一个大前端团队往往既有客户端也有前端,会沉淀一系列的端上的能力。不同的需求会有不一样的技术选型,譬如说一个播放器往往是通过 Native 技术去开发的。我们期望将端上的能力(包括 Flutter Widget、Web、Native 以及三方 SDK 等)进行整合,融合成一个大前端的端开发体系,所以在 Kraken 内我们如何整合端上的这一系列能力呢?同时,我们也期望按需引入,能做到包体积的优化。在不同的业务域,我们期望可以快速地进行定制化开发,快速形成一套垂直业务域的领域能力。


image.png


Kraken 提供了一套扩展能力来解决上述问题,通过渲染能力扩展接口,开发者可以将开发完成的符合标准的 Flutter Widget 以及 Native 的渲染能力快速集成到 Kraken 体系中去,最终通过 JavaScript 来提供一个动态化调用的能力。同样的,通过 MethodChannel,开发者可以通过该通道调用一些 Native 或者 Dart API 的能力,譬如说一些二方或者三方的 SDK 能力。


开发者可以通过扩展能力自定义业务域需要的能力,按需拔插以达到包体积优化的目的。同样的,注册到 kraken 的插件都可以通过 JavaScript 代码控制,提供了动态性。


image.png


下面是一系列在 Kraken 内部扩展 Flutter Widget、Native API 以及 Native 播放器的 Demo。


image.png


下面是提升可交互性。在介绍 Kraken 的可交互性之前,我们先来看一下在 Web 下的一些交互问题。


在 Web 下开发富交互能力的应用时,前端开发者往往需要引入一个额外的 lib 来提供增强的手势能力(譬如说 Hammer.js 这样的手势库)。那么当前端开发者引入 lib 时,就会导致加载 index.html 以后,还需要额外的请求对应的 JS 库,造成一次额外的请求开销的同时延长了首屏的可交互时间。


当用户在屏幕上进行某个操作时,由于用户操作的方式可能是用户的手,也可能是 Apple Pencil 或者鼠标这样的设备。所以在 W3C 标准中,将用户操作可交互应用的触点抽象为一个 pointer,这些 pointer 会根据操作形成一个手势,分别是 down、move、up 三个过程,其中 move 可省略(譬如说 click)。


在 Web 中,需要将这一系列 pointer 给 dispatch 到 element tree 上,通过冒泡将这些 pointer 频繁地发送到 JS 层,然后 JS 再通过封装 Touch API 来完成对交互的识别。这样做带来几个问题,首先频繁地将 pointer 从 C++ 传递到 JS 带来了不必要的开销,此外封装标准的能力也会造成额外的开发成本,易用性并不突出。此时,如果使用社区的一些方案,也会导致非标准化使标准不对齐导致同个应用中的不同页面有不一致的交互体验。


image.png


为了解决上述问题,我们期望从标准化、易用性、标准化几个方面提供一套标准化的交互能力。通过封装底层的 pointer 来得到不同的手势能力,使开发者可以快速开发富交互的应用。


image.png


下面是 Kraken 中增强交互能力的流程图。当用户进行某些交互操作以后,每一个触点的 pointer 会从 Native 传递到 Kraken 中,Pointer 会同时分发给 GestureManager(手势识别器管理类)以及 Scroll 识别器。GestureManager 会识别开发者通过 Web 标准的监听行为(EventTarget.addEventListener)来注册以及分发给对应的手势识别器,同样 Scroll 识别器也会被分发 pointer。这些识别器被加入到 Flutter 的竞争场进行手势竞争,以保证只触发某一个具体操作(交互可控)。Scroll 识别器会触发滚动区域的滚动操作,手势识别器则会通过标准的 Web 流程进行冒泡以及 dispatch,最终开发者通过监听事件完成自定义行为。


image.png


开发应用时,调试能力是必不可少的,前端开发效率高不止要归功于繁荣的生态,友好的开发调试体验一样是提升效率的神器。


image.png


Kraken 抽象了 Inspector 以通过 Chrome DevTools Protocol 来对接 Chrome DevTools,提供了一系列跟前端开发 Web 应用完全一致的调试体验,无论开发者喜欢使用 Console.log 还是通过 JS Debugger ,都可以快速上手。


image.png


image.png


此外,Kraken 也通过支持 HMR 的所有标准的 Web API,来提供局部热更新的能力,使开发 kraken 应用能跟 Web 下一致的局部热更新的调试体验,大大提升了开发者的开发调试体验。


image.png


最后,Kraken 的所有代码都已经开源,Kraken 提供了开放的 TSC 机制期望所有开发者可以平等地交流以及决策,使 Kraken 可以更好地发展,也欢迎更多的开发者一起来共建 Kraken。


Kraken Github:https://github.com/openkraken/kraken

Kraken 官网:https://openkraken.com/

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
硬解析和物理读取与软解析和逻辑读取
参考:http://www.cnblogs.com/chinhr/archive/2009/03/14/1412100.html 001 预备知识 ·Recursive Calls:有时为了执行用户发出的一条SQL语句,Oracle必须执行额外的语句。
611 0
Redis源码解析(1)——源码目录介绍
概念       redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。
832 0
iOS KVO crash 自修复技术实现与原理解析
【前言】KVO API设计非常不合理,于是有很多的KVO三方库,比如 KVOController 用更优的API来规避这些crash,但是侵入性比较大,必须编码规范来约束所有人都要使用该方式。有没有什么更优雅,无感知的接入方式?
5884 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
7735 0
开发那点事系列三 - 由XML解析引起的API设计思考
      谈起XML解析,大家可能第一反应是DOM,SAX模型。没错,在Java领域,无论是Dom4j, Jdom,还是XOM,其底层都会依赖具体的解析器引擎(Crimson or Xerces)去做事,具体的实现后面会有文章陆续探究。今天写这篇文章的主要目的是想和大家分享自己使用Java SE6的StAX API的一些感受,尤其是对其API的设计理念的一个思考,没多少文字,主要是一
1468 0
1326
文章
282
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载