Hummingbird: 在Web上运行Flutter应用

Posted UC国际技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hummingbird: 在Web上运行Flutter应用相关的知识,希望对你有一定的参考价值。


     译者:UC 国际研发 Jothy




今天,我们在 Flutter Live 上宣布了一个消息:我们正尝试在 Web 上运行 Flutter。 这篇文章描述了我们应对挑战的方式,以及该技术的当前状态。 在文末,我们附上了协同工作和嵌入等问题的答案。

Hummingbird: 在Web上运行Flutter应用

让我们快速回顾一下 Flutter 的架构。 Flutter 是一个多层系统,这样高的层更易用,用很少的代码就能表达很多,而较低的层能提供更多的控制,代价是必须处理一些复杂性。 当较高层不能满足开发者的需求时,它们可以降到较低层。 开发者可以访问 Flutter Engine 之上的所有层。

Hummingbird: 在Web上运行Flutter应用Flutter 的 Mobile 架构

在 Flutter 中,Flutter Engine 作为最低级别的库 dart:ui 暴露。它不关心 组件,物理实现,动画或布局(文本布局除外)。它所关心的是如何将图片组合到屏幕上,渲染变成像素。在 dart:ui 上直接编写应用是很困难的。这正是我们创建更高层的原因。

dart:ui 之上的一切是我们所谓的“框架”。它下面的一切都是“引擎”。该框架完全使用 Dart 语言编写。大多数引擎都是用 C++ 编写的,特定于 android 的部分用 Java 编写,而 ios 特定的部分用 Objective-C 编写。 dart:ui 中的一些基本类和函数是用 Dart 编写的,主要用作 Dart 和 C++ 之间的桥梁。

Flutter 还提供插件系统。插件使用指定语言编写,可以直接访问移动生态系统日积月累的 OEM 库和第三方库。你可以使用 Java 或 Kotlin 为 Android 创建插件。 iOS 插件开发是使用 Objective-C 或 Swift。

Hello, The Web

Web 平台已经发展了数十年,包含了许多技术和规范。 有一些涵盖性术语用于描述大量相关功能:html,CSS,SVG,javascript,WebGL。 为了在 Web 上运行 Flutter,我们需要:


  • 编译 Dart 代码:Flutter 是用 Dart 编写的,我们需要在 Web 上运行 Dart。

  • 选择要在 Web 上运行的 Flutter 子集:在 Web 上运行所有 Flutter 代码是不切实际的。 其中一些是特定于平台的,例如 Android 和 iOS。

  • 选择足够的 Web 功能子集:随着时间的推移,Web 平台会累积重复的功能。 例如,你可以使用 HTML + CSS,SVG,Canvas 和 WebGL 绘制图形。

从 Dart 诞生之初,它就一直在编译 JavaScript。 现在有许多重要的应用都从 Dart 编译为 JavaScript,并在生产环境中运行。 Flutter 的编译策略依赖于同样的基础设施。

当我们开始探索时,我们面临着 UI 渲染的几种选择。 我们很快意识到,要想支持的特定 Flutter 层,决定了我们将用什么 Web 技术。 我们构建了三个原型:


  • 仅仅是 Widgets(组件):这个原型实现了 Flutter 的 widget 框架,并提供了一组核心布局 widget 作为构建自定义 widget 的基础。 对于布局和定位,它依赖于 Web 的内置功能,例如 flexbox,grid 布局,浏览器滚动(通过 overflow:scroll 实现)等。

  • Widgets + 自定义布局:此原型包括 Flutter 的布局系统(由 RenderObject 提供),但将渲染对象直接映射到 HTML 元素。

  • Flutter Web Engine:这个原型保留了 dart:ui 之上的所有层,并提供了一个在浏览器中运行的 dart:ui 实现。

One of the most valuable features of Flutter is that it is portable across platforms. While you can (and sometimes are encouraged to) write custom platform-specific code, the code that does not need to be different across platforms can be shared. This allows writing applications targeting multiple platforms with a single codebase.

Flutter 最有价值的功能之一是它可以跨平台移植。 你完全可以(有时甚至被鼓励)编写自定义的特定平台代码,代码无需跨平台定制即可共享。 意味着使用单个代码库就可以编写面向多个平台的应用。

在尝试将几个示例应用移植到 Web 之后,我们意识到原型 #1 和 #2 不能提供 Flutter 开发者喜欢的可移植性级别。 因此,我们决定使用 Flutter Web Engine 设计的原型 #3,因为它有着平台之间最高的框架级代码重用:

Hummingbird: 在Web上运行Flutter应用Flutter的Web架构 (Hummingbird)

既然我们知道我们想要实现整个 dart:ui  API,我们需要选择一组 Web 技术来构建。 Flutter 一次渲染一帧 UI。 在每个帧内,Flutter 会构建 widgets,执行布局,最后在屏幕上绘制它们。

构建 Widgets

Widget 构建机制不依赖于应用运行的环境。该过程只是实例化内存中的对象,跟踪其状态、以及状态变更何时计算系统低级别的最小更新,布局和绘制等。 将此部分移植到 Web 上非常简单。 在 Dart 团队用 dart2js 中实现了 super-mixin 支持之后,编译器将所有 widget 和 widget frame 都编译成了 JavaScript,几乎没有 issue 产生。


布局

布局系统有点棘手。 最大的挑战是文本布局。 除了 Center,Row,  Column,Stack,Scrollable,Padding,Wrap 等之外的所有内容都由框架布局,因此无需修改即可编译到 Web。

在 Flutter 中,你可以创建 Paragraph 对象并调用其 layout() 方法来实现文本布局。 不幸的是,Web 缺少直接的文本布局 API。 我们用来测量文本布局属性的技巧是:先让浏览器布局,然后从 DOM 元素中读回相关属性。

布局文本段落时,Flutter 会测量段落的高度,宽度,最大内在宽度,最小内在宽度以及字母和表意基线。 这些属性如下所示。

Hummingbird: 在Web上运行Flutter应用Paragraph layout attributes

你可以在 Flutter 的 Paragraph 文档中找到更多详细信息。

要测量这些属性,我们首先在 HTML DOM 元素中放置一个段落,然后读取元素的维度。 这会引起浏览器布局。 例如,要获取元素的宽度和高度,我们调用 offsetWidth 及 offsetHeight。 为了测量基线,我们将段落放置在一个元素中,该元素配置为使用 flex 行进行布局。 在段落旁边,我们放置另一个名为 probe 的元素。 因为 probe 与文本的基线对齐,所以调用 getBoundingClientRect 就可以得到基线。 我们使用类似的技巧来测量最小和最大固有宽度。


Painting(绘制)

不得不提的是,我们得绘制 widgets。 对这个区域的探索最是麻烦,它仍然是我们的研究中最活跃的领域。 在框架最后,我们所有的 widget 都需要在屏幕上绘制成像素。 在浏览器中,这意味着它们必须归结为 HTML / CSS,Canvas,SVG 和 WebGL 的某种组合。

我们还没有看过 WebGL,主要是因为它级别较低,并且要求我们重新实现浏览器已经可以做的事情,例如文本布局和光栅化 2D 图形。另一个原因是我们还没有弄清楚非 Flutter 组件与 WebGL 如何结合才能实现可访问性,文本选择,和组合。

我们的早期原型为每个 RenderObject 生成了一个 HTML 元素。 结果符合预期,但最后事实却证明 API 的变化太大了。 我们必须用 Flutter 维持一个巨大的代码增量,所以我们搁置了这个想法。

我们目前正在同时探索两种方法:

  • HTML+CSS+Canvas

  • CSS Paint API

HTML+CSS+Canvas


基于这种方法,我们将框架生成的图片分类为使用 HTML + CSS 表达的图片以及使用 Canvas 2D 表达的图片。然后,我们输出结合了 HTML,CSS 和 2D 画布的 HTML DOM。

我们更喜欢 HTML + CSS,因为它受浏览器的显示列表支持。这意味着我们可以把图片的光栅化优化留给浏览器的渲染引擎去做。并且,我们还可以应用任意变换,尤其是旋转和缩放,而不必担心像素化。我们将此画布实现称为 DomCanvas。

如果我们无法使用 HTML + CSS 表达图片,我们会用 Canvas。 Canvas 2D 允许我们绘制几乎所有的 Flutter 绘图命令。如果将 Flutter 的 Canvas 与 Web 的 CanvasRenderingContext2D 进行比较,你会发现许多相似之处。在 Canvas 上绘画很高效,因为它不会创建需要随时间维护的可变树节点,如 HTML DOM 或 SVG。

2D Canvas 的一个挑战是浏览器将其表示为位图,即存储 Width x Height 像素的内存缓冲区。因此,缩放 canvas 会导致像素化。如果缩放导致图片大小变化,我们也需要调整 canvas 大小。我们发现分配 canvas 操作相当昂贵,因此方案改成调整它们的大小。最重要的是,当将多个 canvas 合成到同一页面上时,浏览器必须执行栅格合成,这也需要配置。合成栅格与显示列表的方式不同。你可以将多个显示列表绘制到同一个内存缓冲区中。我们调用 Canvas 2D 支持的 canvas 实现 BitmapCanvas。我们正在发掘使位图 canvas 更高效的方法。

为了表达 Flutter 的不透明度,变换,偏移,剪辑矩形和其他图层,我们使用纯 HTML 元素。例如,不透明度层变为 <flt-opacity> 元素,其上具有 opacity CSS 属性,变换图层变为带有 transform CSS 属性的 <flt-transform> 元素,剪辑 rect 变为使用 overflow: hidden 的 <flt-clip-rect >

完成所有操作后,框架将作为 HTML 元素树呈现在页面上,其中 DomCanvas 和 BitmapCanvas 作为叶节点。举个例子

以上是关于Hummingbird: 在Web上运行Flutter应用的主要内容,如果未能解决你的问题,请参考以下文章

Gibson Hummingbird Koa 特别限量版

只有在 Flutter 应用程序上使用 Web 时,如何才能禁用某些功能?

如何分别在android和ios上本地运行flutter应用程序?

Android Studio 错误:无法找到 adb 位置

创建一个不依赖web的flutter项目

如何更新在 Firebase 托管中部署的 Flutter Web 版本