Hummingbird: 在Web上运行Flutter应用

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

image.png

原文作者:Yegor Jbanov

译者:UC 国际研发 Jothy


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

image.png

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

image.png

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,因为它有着平台之间最高的框架级代码重用:
image.png

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 会测量段落的高度,宽度,最大内在宽度,最小内在宽度以及字母和表意基线。 这些属性如下所示。
image.png

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 元素。例如,不透明度层变为 元素,其上具有 opacity CSS 属性,变换图层变为带有 transform CSS 属性的 元素,剪辑 rect 变为使用 overflow: hidden 的 。

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

Sample HTML DOM structure of a frame

Flutter Engine 中的等效 Flutter layer tree(称为 flow layer)如下所示:
image.png

Sample Flutter Engine layer structure

它们的结构非常相似。 最大的区别是,在 Web 上,我们必须根据内容选择不同的图片实现。

HTML + CSS + Canvas 适用于所有现代浏览器。 但是,我们已经在展望未来:

CSS Paint API
CSS Paint 是一个新的 Web API,是 Houdini 的更大组成部分。 Houdini 是多个浏览器厂商合作的项目,旨在向开发者展示 CSS 引擎的某些部分。 特别的是,CSS Paint API 允许开发者在这些元素请求绘制时将自定义图形绘制成 HTML 元素。 例如,你可以将元素背景的绘制分配给自定义 CSS 绘制器。 它与 canvas 非常像,但有以下重要区别:

这个绘画不是由核心 JavaScript 独立完成的,而是由一个叫做 paint worklet 的东西完成的。 它有点像 web worker,因为它有自己的内存空间。 它会在 DOM 更改提交之后,在浏览器的绘制阶段执行绘制工作。

CSS paint 由显示列表支持,而不是位图。 这真是两全其美 - 2D canvas 般的绘画效率和无像素化。

目前 CSS paint 不支持绘制文本。

在撰写本文时,Chrome 和 Opera 是唯一在正式版本中支持 CSS Paint 的浏览器。 而其他浏览器正处于发布各自实现的不同阶段。

我们在 Flutter for Web 中对 CSS Paint API 进行了实验性支持,它已经展现出良好的结果,特别是在性能方面。 我们的实现只是将 paint 命令序列化为自定义 CSS 属性。 paint worklet 读取这些命令并执行它们。 我们使用像

这样普通的 HTML 元素来渲染文本。

我们当前的序列化机制不是特别有效 - 它是一个嵌套列表转换成的 JSON 树 - 但 Houdini 项目的一部分是添加对类型化数组的支持。 当它可用时,我们会将绘制命令编码为类型化数组而不是 JSON 字符串。 类型化数组是可转移的,这意味着它们可以通过引用从主 JavaScript 传递到 paint worklet,而不复制内存。

协同和嵌入

从 Flutter 调用 Dart 库

Flutter Web 应用可以访问当前在 Web 上运行的所有 Dart 库。

从 Flutter 调用 JavaScript 库

Flutter Web 应用完全支持 Dart 的 JS-interop 软件包:package:js和 dart:js。

在 Flutter Web 应用中使用 CSS

目前,Flutter 假定完全控制网页的正确性和性能。 例如,我们只使用遵循某些性能指南的一小部分 CSS,例如 https://csstriggers.com/。 在页面上随意使用 CSS 可能会导致 Flutter 出现不可预期的后果。

在 Flutter for Web 应用中避免使用 CSS 的另一个原因是,在设计时,Flutter 需要在渲染框架时知道所有布局属性。 CSS 充当黑盒。 例如,如果要显示可滚动的窗口 widget 列表,则必须实例化并为所有 widgets 生成 HTML 并应用必要的 CSS 属性(例如,flex-direction row 和 overflow:scroll)。 然后浏览器将所有内容都布局并将其渲染到屏幕上。 应用代码不参与布局过程。

最后,本着保持 Flutter 代码可跨平台移植的精神,我们尽量避免使用 CSS,因此我们可以在 Android 和 iOS 上本机运行相同的代码。

将 Flutter 嵌入现有的 Web 应用中

我们还没有为此添加适当的支持,但我们打算在将来探索。 我们正在考虑的方法是 和 shadow DOM。

在 Flutter 中嵌入非 Flutter 组件

我们还未支持在 Flutter Web 应用中嵌入非 Flutter 组件 - 自定义元素、React 组件、Angular 组件,但我们打算在将来探索。 有可能是使用平台视图将外部内容放入 Flutter Web 应用中。需要考虑的是外部内容可能对应用的性能和正确性产生影响。 因为非 Flutter 组件可能包含任意 CSS,如上所述,它可能会有问题。 需要更多的研究。

可移植性

我们的目标是尽可能多地将框架移植到 Web 上。 但是,这并不意味着任何 Flutter 应用将在 Web 上运行而不更改代码。 Flutter Web 应用仍然是一个 Web 应用; 它在浏览器中被沙箱化,只能执行 Web 浏览器允许的操作。 例如,如果你的 Flutter 应用使用 Web 未实现的本机插件(例如 ARCore),你将无法在 Web 上运行该应用。 同样,也无权限直接访问文件系统或低级网络。

当前状态

我们构建了足够的 Web 引擎来渲染大部分 Flutter Gallery。 我们还未移植 Cupertino widgets,但所有 Material widgets,Material Theming,以及 Shrine 和 Contact Profile 演示应用均已运行在 Web 上。

Flutter running in desktop Chrome 演讲视频
https://www.youtube.com/watch?v=5IrPi2Eo-xM

源代码在哪里?
我们计划很快开源这个项目,并很高兴与开源社区分享。 该项目最初是作为 Google 内部源代码树的一项探索而开始的。 待代码稳定后,我们打算将开发转移到 GitHub,我们有机会将其从内部基础架构中剥离出来。 与此同时,如果您在 github.com/flutter 组织下看到与 Web 相关的 pull 请求,请不要感到惊讶!

结论

希望这篇文章能让你了解我们正在解决的问题,以帮助 Flutter 在 Web 上更好运行。 欢迎表达您的观点和意见。

请继续关注 Google I/O 2019!

英文原文:

https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8

目录
相关文章
|
10月前
|
存储 Android开发
如何查看Flutter应用在Android设备上已被撤销的权限?
如何查看Flutter应用在Android设备上已被撤销的权限?
488 64
|
9月前
|
前端开发 算法 API
构建高性能图像处理Web应用:Next.js与TailwindCSS实践
本文分享了构建在线图像黑白转换工具的技术实践,涵盖技术栈选择、架构设计与性能优化。项目采用Next.js提供优秀的SSR性能和SEO支持,TailwindCSS加速UI开发,WebAssembly实现高性能图像处理算法。通过渐进式处理、WebWorker隔离及内存管理等策略,解决大图像处理性能瓶颈,并确保跨浏览器兼容性和移动设备优化。实际应用案例展示了其即时处理、高质量输出和客户端隐私保护等特点。未来计划引入WebGPU加速、AI增强等功能,进一步提升用户体验。此技术栈为Web图像处理应用提供了高效可行的解决方案。
|
10月前
|
缓存 前端开发 JavaScript
Flutter Demo 的快速编译与运行
Flutter Demo 的快速编译与运行
362 12
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
796 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
10月前
|
缓存 Android开发 开发者
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
1918 62
|
10月前
|
开发工具 Android开发 开发者
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
700 61
|
8月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。该方案设计目标是实现通用性,适用于各种网络架构和业务场景。
265 1
|
9月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。
357 7
|
10月前
|
缓存 IDE 开发工具
Flutter Demo 的快速编译与运行
Flutter Demo 的快速编译与运行
423 15
|
12月前
|
缓存 Java 测试技术
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
1743 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了