重磅|庖丁解牛之——Flutter for Web

Flutter for Web

在2018年冬的Flutter 1.0伦敦发布会上,Flutter产品经理Tim Sneath通过一个滑动拼图的例子介绍了如何让Flutter运行在Web之上。这一当时代号HummingBird的项目后来被重命名为flutter_web,并最终合入了master分支。

Flutter Web想在单代码库的情况下,使Flutter应用拥有Web支持。这样开发者使用Dart编写的Flutter应用可以被部署到任意的Web服务器上,或嵌入到浏览器中。开发者可以使用Flutter的所有特性,也不需要特殊的浏览器插件支持。

就最新的Flutter1.9.x而言,Flutter Web还处于技术预览版阶段,离真正应用到生产环境中还是有一些距离的。

设计

那么Flutter Web是怎么做到这一切的呢?这就要从Flutter的原理说起。Flutter框架的设计如下所示:

其中,Flutter Framework是使用纯Dart开发的。我们将其分为两部分,渲染和逻辑。就渲染而言,其最终会表示为dart:ui中提供的TextBox,Picture,Image等实例对象,再通过native方法(实现dart调用C++)调用Skia,Text等C++库,最终渲染在屏幕上,逻辑部分则被Dart Runtime执行。不难看出,要实现在Web上运行Flutter,要解决两个问题。Dart如何运行在Web上以及dart:ui中的native方法如何通过标准Web的方式来实现。就前者而言,dart2js是一个已有的成熟框架,所以问题的重点就在于如何通过标准Web的方式去实现一个dart:ui库。这也就是目前Flutter Web的设计原理:

在Flutter Web的设计之初,主要考虑了两个方案用于Web支持:

  • HTML+CSS+Canvas
  • CSS Paint API

方案1具有最好的兼容性,它优先考虑HTML+CSS表达,当HTML+CSS无法表达图片的时候,会使用Canvas来绘制。但2D Canvas在浏览器中是位图表示,会造成像素化下的性能问题。

方案2是新的Web API, 属于Houdini的组成部分。Houdini提供了一组可以直接访问CSS对象模型的API,使得开发者可以去书写代码并被浏览器作为CSS加以解析,这样在无需等待浏览器原生的支持下,创造了新的CSS特性。它的绘制并非由核心Javascript完成,而是类似Web Worker的机制。其绘制由显示列表支持,而不是位图。但目前CSS Paint API不支持文本,此外各家厂商对齐支持也并不统一。

鉴于此,目前Flutter Web使用的是基于方案1的实现。

环境准备

Flutter环境

flutter doctor -v[✓] Flutter (Channel master, v1.10.6-pre.61, on Mac OS X 10.15 19A558d, locale en-CN)    • Flutter version 1.10.6-pre.61 at /Users/kylewong/Codes/Flutter/alibaba-flutter/flutter    • Framework revision 7bf9aea254 (4 hours ago), 2019-09-25 00:37:12 -0700    • Engine revision 63949eb0fd    • Dart version 2.6.0 (build 2.6.0-dev.0.0 69b5681546)...[✓] Chrome - develop for the web    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome[✓] Android Studio (version 3.5)    • Android Studio at /Applications/Android Studio.app/Contents    • Flutter plugin version 39.0.3    • Dart plugin version 191.8423    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)

Web环境

在flutter的master分支上,开发者可以通过下方命令检查当前是否打开了Web支持:

kylewong@KyleWongdeMacBook-Pro web_dig % flutter devices3 connected devices:MHA AL00          • GWY7N16A31002764                         • android-arm64  • Android 9 (API 28)Chrome            • chrome                                   • web-javascript • Google Chrome 77.0.3865.90Server            • web                                      • web-javascript • Flutter Tools

如果不能看到Chrome/Server这两个设备,可以通过以下命令打开支持:

flutter config --enable-web

这个命令会将配置项保存到用户Home目录下的.flutter_settings中,一个典型的内容如下所示:

{  "enable-web": false,  "ios-signing-cert": "iPhone Developer: Kang Wang (xxx)"}

dart2js配置修改

以flutter自带的gallery为例,默认的flutter web实现下,生成的js如下所示:

可以看到此js代码可读性很差(变量名,格式等),大小为2.2MB。这是因为flutter构建过程中开启了dart2js命令的O4优化项所致。为了方便我们分析和调试,我们对此其进行如下修改:

O0将禁止很多优化,修改后的效果如下所示:

可以看到,大小增加了不少,但可读性上好很多,除特殊说明外,本文将在O0优化项下展开。

原理剖析

Gallery上的表现对比

我们首先基于Flutter提供的Gallery项目,比较下其在Mobile和Web上的表现(此处使用Flutter Web默认优化级别):

Flutter Native vs Flutter Web:

可以看出,Flutter Web在完备性上还是比较不错的,但依然有一些问题,比如本地图片在Android设备上显示正常,在iOS上却无法正常显示,网络图片则是正常的。

在Mobile/Web开发中,常见的元素包括图片,文字,形状,手势等,接下来,我们逐一进行剖析。

图片的实现

以如下代码为例:

import 'package:flutter/material.dart';void main() => runApp(Image.asset('assets/1.png'));

其运行效果如下(左侧为Native,右侧为Web):

其Native与Web简要原理对比如下所示:

在flutterwebsdk中最终调用html库(dart-sdk自带)绘制的代码如下:

flutter_web_sdk/lib/_engine/engine/bitmap_canvas.dart@overridevoid drawImageRect(    ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint) {  // TODO(het): Check if the src rect is the entire image, and if so just  // append the imgElement and set it's height and width.  print('KWLM04');  final HtmlImage htmlImage = image;  ctx.drawImageScaledFromSource(    htmlImage.imgElement,    src.left,    ...    dst.height,  );}

相对应地,通过 flutter build web--release--verbose生成的main.dart.js中部分代码如下:

此部分对应上述bitmap_canvas.dart中的drawImageRectdrawImageRect$4: function(image, src, dst, paint) {    ...    P.print("KWLM04");    H.interceptedTypeCheck(image, "$isHtmlImage");    J.drawImageScaledFromSource$9$x(this.get$ctx(), image.imgElement, src.left, src.top, src.get$width(src), src.get$height(src), dst.left, dst.top, dst.get$width(dst), dst.get$height(dst));},👇drawImageScaledFromSource$9$x: function(receiver, a0, a1, a2, a3, a4, a5, a6, a7, a8) {    return J.getInterceptor$x(receiver).drawImageScaledFromSource$9(receiver, a0, a1, a2, a3, a4, a5, a6, a7, a8);},👇

最终调用到了CanvasRenderingContext2D.drawImage这一标准W3C的API。

文本的实现

以如下代码为例:

import 'package:flutter/material.dart';void main() => runApp(Text('Hello Flutter!'));

其运行效果如下(左侧为Native,右侧为Web):

其Native与Web简要原理对比如下所示:

在flutterwebsdk中最终调用html库(dart-sdk自带)构建和添加Element的代码如下:

flutter_web_sdk/lib/_engine/engine/dom_canvas.dart@overridevoid drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {  print('KWLM18');  final html.Element paragraphElement =      _drawParagraphElement(paragraph, offset, transform: currentTransform);  currentElement.append(paragraphElement);}
flutter_web_sdk/lib/_engine/engine/engine_canvas.darthtml.Element _drawParagraphElement(  EngineParagraph paragraph,  ui.Offset offset, {  Matrix4 transform,}) {  print('KWLM25');  assert(paragraph._isLaidOut);  final html.Element paragraphElement = paragraph._paragraphElement.clone(true);  final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;    ...  return paragraphElement;}

相对应地main.dart.js中部分代码如下:

此部分对应上述dom_canvas.dart中的drawParagraphdrawParagraph$2: function(paragraph, offset) {   var paragraphElement;   H.interceptedTypeCheck(paragraph, "$isParagraph");   H.interceptedTypeCheck(offset, "$isOffset");   P.print("KWLM18");   paragraphElement = H._drawParagraphElement(paragraph, offset, this.get$currentTransform(this));   J.append$1$x(this.get$currentElement(), paragraphElement);},👇_drawParagraphElement: function(paragraph, offset, transform) {  var paragraphElement, paragraphStyle, style, t1;  P.print("KWLM25");  paragraphElement = H.interceptedTypeCheck(J.clone$1$x(paragraph._paragraphElement, true), "$isElement0");  paragraphStyle = paragraphElement.style;  (paragraphStyle && C.CssStyleDeclaration_methods).set$position(paragraphStyle, "absolute");  ...  return paragraphElement;},👇

从文本这个例子不难看出,对于可以通过HTML+CSS形式表达的元素,flutter web将其最终翻译成Element+CSS Style形式动态生成类似静态HTML+CSS描述的内容,最终完成内容的渲染。

形状的实现

以如下代码为例:

import 'package:flutter/material.dart';void main() => runApp(Container(decoration: BoxDecoration(color: Colors.red)));

其运行效果如下(左侧为Native,右侧为Web):

其Native与Web简要原理对比如下所示:

在flutterwebsdk中最终调用html库(dart-sdk自带)构建Element(添加部分同文本)的代码如下:

flutter_web_sdk/lib/_engine/engine/dom_canvas.dart@overridevoid drawRect(ui.Rect rect, ui.PaintData paint) {  print('KWLM47');  assert(paint.shader == null);  final html.Element rectangle = html.Element.tag('draw-rect');  ...  currentElement.append(rectangle);}

相对应地main.dart.js中部分代码如下:

此部分对应上述dom_canvas.dart中的drawRectdrawRect$2: function(rect, paint) {    var rectangle, isStroke, t1, t2, t3, left, right, $top, bottom, effectiveTransform, translated, style, cssColor, _this = this;    H.interceptedTypeCheck(rect, "$isRect");    H.interceptedTypeCheck(paint, "$isPaintData");    P.print("KWLM47");    rectangle = W.Element_Element$tag("draw-rect");    ...    J.append$1$x(_this.get$currentElement(), rectangle);}👇

对于本例中的形状,也是通过HTML+CSS的方式实现的。

触摸事件的实现

以如下代码为例:

import 'package:flutter/material.dart';void main() => runApp(GestureDetector(child: Text('Click me!',style: TextStyle(fontSize: 50),), onTap: (){  print('KWLM called!');}));

其运行效果如下(左侧为Native,右侧为Web):

其Native与Web简要原理对比如下所示:

此例中的PointerBinding由dartsdk.js提供,其提供了从Window获取事件回调的机制,并最终调用到了WidgetsFlutterBinding(也是GestureBinding)的handlePointerDataPacket$1方法,后续的路由机制同Native情景下的Flutter部分。

优缺点

优点

从目前Flutter Web选取的技术路线来说,HTML+CSS+Canvas这种方式具有最好的兼容性,这样开发者开发的Flutter代码(不包括Plugin部分对于Native的扩展)将零成本地转成标准Web展示,这一低成本扩展到Web平台带来的优势还是很明显的。

不足

尽管其优势很明显,也面临一些不足的问题 a. 包大小过大的问题

目前dart2js本身并没有针对小型程序做出优化,即使是本文中的手势这么简单的代码,Flutter Web最终生成的大小也有560KB, 无法满足要求。

但从理论上来说,通过对dart2js本身做出合理的优化(鉴于dart/flutter整个的开源设计),我们可以将Flutter Web依赖的基础SDK全集嵌入应用中(或者按需下载的方式),将真正的业务代码与SDK分离,也是有可能将其大小降低的。

b. 功能不完备的问题 比如在flutter_gallery的例子中Safari上图标展示为方框的问题。

c. 性能的问题 当需要用到BitmapCanvas比较多的时候,Element对象直接的光栅化,会导致在一些诸如缩放等的场景下,面临性能的问题。当然缩放的问题在移动设备的场景下也是有可能避免的。

小结

总体而言,Flutter Web具有优秀的设计。它基于dart:js和dart:html这些成熟的框架,通过将与Native相关的dart:ui库重写的方式,很好地解决了Flutter扩展到Web平台上的问题。对于上层开发者而言,完全不用去做任何修改,即可产生一套符合Web标准的代码,显示和行为也同原始设计保持一致。虽然目前Flutter Web还不够成熟,存在一些诸如包大小性能等问题,但基于Flutter和Flutter Web的良好分层设计,我们有理由相信随着时间的推移和社区成熟,这些问题终将得到改善或解决。

Image placeholder
qwerads
未设置
  96人点赞

没有讨论,发表一下自己的看法吧

推荐文章
基于JS的高性能Flutter动态化框架MXFlutter

导语:18年10月份,手机QQ看点团队尝试使用Flutter,做为iOS开发,一接触到Flutter就马上感受到,Flutter虽然强大,但不能像RN一样动态化是阻碍我们使用她的唯一障碍了。看Goog

Flutter路由项目实战之fluro

github:https://github.com/zhengzhuan...关于flutter路由,在小项目中,就按照原生写法,但是在大型项目中,这样的我就不会进行推荐,我这里使用的fluro路由管

闲鱼Flutter互动引擎系列——整体设计篇

什么是Candy引擎Candy引擎是闲鱼技术团队设计开发的一款:APP嵌入式的、轻量级的、易于开发、性能稳定的互动引擎;绘制系统高度融合Flutter体系,游戏场景和FlutterUI支持无缝混排;动

Stylus系列——webpack-spritesmith配合stylus使用示例

一、前言基于Webpack的CSSSprites实现方案,若是直接在html中调用雪碧图图标已经很方便,但是实际开发过程可能遇到需要在伪元素中使用雪碧图,或者需要hover切换另一个图标,这种情况下就

跨平台开发的救星-让我们来了解一下flutter

第一次看文章的朋友可以关注我,会不定期发布Android面试内容、进阶专题等等。简介很多人已经用上了flutter,今天就来介绍一下Flutter架构Flutter框架分三层 Framework,En

Flutter高内聚组件怎么做?闲鱼打造开源高效方案!

fish_redux是闲鱼技术团队打造的开源flutter应用开发框架,旨在解决页面内组件间的高内聚、低耦合问题。开源地址:https://github.com/alibaba/fish-redux从

揭秘!一个高准确率的Flutter埋点框架如何设计

背景用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Na

走近科学,探究阿里闲鱼团队通过数据提升Flutter体验的真相

背景闲鱼客户端的Flutter页面已经服务上亿级用户,因此用户体验尤其重要,完善Flutter性能稳定性监控体系,以便及早发现线上性能问题,也可以作为用户体验提升的衡量标准。那么Flutter的性能到

Flutter环境搭建记录

1.下载安装包之后执行flutterdoctor报错:zsh:commandnotfound:flutter下载的是源码,去这里下载SDK 2.执行flutterdoctor,按照提示升级xcode、

Spring WebFlux 的设计及工作原理剖析

前言 Spring5发布有两年了,随Spring5一起发布了一个和SpringWebMvc同级的SpringWebFlux。这是一个支持反应式编程模型的新框架体系。反应式模型区别于传统的MVC最大的不

Spring Boot 中的响应式编程和 WebFlux 入门

Spring5.0中发布了重量级组件Webflux,拉起了响应式编程的规模使用序幕。WebFlux使用的场景是异步非阻塞的,使用Webflux作为系统解决方案,在大多数场景下可以提高系统吞吐量。Spr

阿里巴巴重磅发布含光 800 芯片:顶 10 个 GPU

2019年9月25日,2019杭州·云栖大会在云栖小镇举行。回顾历届大会,每年都有那么几件大事情值得关注,比如提出“DT时代”、“五新”概念,宣布建设“城市大脑”,成立达摩院、半导体公司“平头哥”等

飞天 AI 平台重磅发布!阿里拥抱 AI 这五年

2014年,一个名为“iDST”的神秘部门的诞生开启了阿里的人工智能新征程,5年后,初长成的达摩院成为了阿里AI崛起的代表性力量。如今,阿里已跻身全球人工智能技术研发的第一梯队,成为时下这AI技术洪

SmartX年中重磅发布:极简IT为生产就绪

超融合基础设施市场不断发展壮大的这几年,专注于提供超融合产品与解决方案的SmartX(北京志凌海纳科技有限公司)声名鹊起,成为国内领先的提供商。这家成立于2013年的公司,一直保持着最初的愿景和使命—

谷歌云重磅推出混合云平台Anthos,兼容竞争对手云服务 | Google Cloud Next’19

大数据文摘出品作者:蒋宝尚、周素云当地时间4月9日,谷歌云年度盛会GoogleCloud Next’19在旧金山的Moscone召开。在会上,谷歌云的新任CEO,曾经的甲骨文二号人物ThomasKur

#超级链重磅产品发布,倒计时3天#

大幕即将拉开,距离百度超级链重磅产品发布还有3天,扫码入群参与线上发布会。

GoWeb教程_08.0. Web 服务

Web服务可以让你在HTTP协议的基础上通过XML或者JSON来交换信息。如果你想知道上海的天气预报、中国石油的股价或者淘宝商家的一个商品信息,你可以编写一段简短的代码,通过抓取这些信息然后通过标准的

GoWeb教程_08.2. WebSocket

WebSocket是HTML5的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信,许多浏览器(Firefox、GoogleChrome和Safari)都已对此做了支

GoWeb教程_13.0. 如何设计一个 Web 框架

前面十二章介绍了如何通过Go来开发Web应用,介绍了很多基础知识、开发工具和开发技巧,那么我们这一章通过这些知识来实现一个简易的Web框架。通过Go语言来实现一个完整的框架设计,这框架中主要内容有第一

GoWeb教程_14.0. 扩展 Web 框架

第十三章介绍了如何开发一个Web框架,通过介绍MVC、路由、日志处理、配置处理完成了一个基本的框架系统,但是一个好的框架需要一些方便的辅助工具来快速的开发Web,那么我们这一章将就如何提供一些快速开发

react-native中IOS的webview和js层通信 - UIWebview

前言在9012的最后一篇写到了在rn中安卓的webview的通信原理,而作为0202年的第一篇,继续讨论上年rn中webview通信剩下的部分。背景:对于webview,了解过的人都知道在ios端会存

使用html-webpack-plugin对HTML文件进行预处理

一、前言先整理一波之前和webpack相关的文章: 使用Webpack对CSS文件进行后处理 基于Webpack的CSSSprites实现方案 Stylus系列——webpack-spritesmit

InfluxDB 管理工具

配置 vim/etc/influxdb/influxdb.conf[admin] Determineswhethertheadminserviceisenabled. enabled=true The

influxDB集群模式实践

influxDB数据库以其优秀的时序数据存储和查询能力,在处理和分析类似资源监控等时序数据方面得到了广泛的应用。而influxDB自带的各种特殊函数如求平均值、标准差、随机取数据,使数据分析变得十分方