写点什么

庖丁解牛之——Flutter for Web

  • 2019-10-12
  • 本文字数:5320 字

    阅读完需:约 17 分钟

庖丁解牛之——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 的良好分层设计,我们有理由相信随着时间的推移和社区成熟,这些问题终将得到改善或解决。


本文转载自公众号闲鱼技术(ID:XYtech_Alibaba)


原文链接


https://mp.weixin.qq.com/s/krR2XsDXvakMlZWbV-VvSg


2019-10-12 08:003690

评论 1 条评论

发布
用户头像
Flutter for Web还是有很大诱惑力的.
2019-10-14 10:24
回复
没有更多了
发现更多内容

停止维护的CentOS6,怎么使用yum?

运维研习社

Linux 5月日更

【技术干货】文件系统中的“锁”

焱融科技

容器 分布式 云原生 高性能 文件存储

前端项目上传图片,压缩,拍照图片旋转解决方案

Vue js canvas axios

Java程序员面试必备——过得了面试官,过不了HR?我教你

比伯

Java 编程 架构 程序人生 计算机

IM扫码登录技术专题(三):通俗易懂,IM扫码登录功能详细原理一篇就够

JackJiang

即时通讯 IM 扫码

拥有一个高性能低延时数据库是什么样的体验?

华为云开发者联盟

数据库 华为云 GaussDB GaussDB(for Cassandra) 低延时

被解救的代码 - 代码即服务时代来了!

Serverless Devs

阿里云 Serverless 云原生

凭借师兄甩给我的通关秘籍,顺利拿到字节Offer

学Java关注我

Java 编程 架构 面试

苹果移动设备用什么管理比较好?有什么推荐?

懒得勤快

imazing 手机管理

消息队列的两种模式

五分钟学大数据

kafka 5月日更

阿里大牛亲码 Spring AOP详解笔记全网开源,学透并发只需3天

飞飞JAva

spring aop

高德 Serverless 平台建设及实践

Serverless Devs

阿里云 Serverless 云原生

干好开发者关系的十个职业发展秘诀

开发者关系

开发者关系 技术运营 DevRel

青海大学智慧微能源数字孪生可视化系统

ThingJS数字孪生引擎

大前端 可视化 3D可视化 数字孪生

一文带你全面了解java对象的序列化和反序列化

华为云开发者联盟

Java 序列化 java对象 反序列化 Serializable接口

python解释器+pycharm的安装

Geek_6370d5

#python学习之路

如何下载和保存YouTube上的中英双语字幕和视频

flyfk

字幕

GitHub霸屏文章!清华教授手写保姆级笔记Scala - 类,网友:太香了

牛哄哄的java大师

Java scala

云厂商下一块必争之地就是它了!

Serverless Devs

Serverless 云原生

GitHub开源的文言文编程语言、程序生成中国山水画、格律诗编辑程序

不脱发的程序猿

GitHub 开源 编程语言 传统文化

10个 解放双手的 IDEA 插件,这些代码都不用写(第二弹)

程序员小富

Java 后端 IDEA

JavaScript设计模式之单例模式

程序员海军

JavaScript 大前端 设计模式 单例模式

阿里P7:每个码农都应该知道的MySQL主从复制方法,看这篇就够了

牛哄哄的java大师

Java MySQL 数据库

消除数据孤岛,华为云DRS让一汽红旗ERP系统数据活起来

华为云开发者联盟

数据库 GaussDB 数据孤岛 华为云DRS ERP

5月20日,GaussDB将有大事发生

华为云开发者联盟

数据库 云原生 华为云 GaussDB TechWave

一线大厂最新总结Spring Security Oauth2.0认证授权全彩笔记

Java架构追梦

Java 阿里巴巴 架构 面试 spring security

Apache Hue介绍

大数据技术指南

hue 5月日更

来了!这份阿里P7大佬梳理的Java注解和反射精髓笔记,信息量过大

飞飞JAva

Java

414天前,我以为这是编程玄学...

why技术

Java JVM JMM

【智慧农业】从“看天吃饭”到“知天而作”,乡村振兴全靠 TA

IoT云工坊

人工智能 物联网 智慧农业 庭院灌溉 温室大棚

300条数据变更引发的血案-记某十亿级核心mongodb集群部分请求不可用故障踩坑记

杨亚洲(专注MongoDB及高性能中间件)

数据库 mongodb 架构 MySQ 分布式数据库mongodb

庖丁解牛之——Flutter for Web_语言 & 开发_正物_InfoQ精选文章