共计 4517 个字符,预计需要花费 12 分钟才能阅读完成。
首先感谢 @栖冰 @祖建国 一起对 FFW 的预研做的投入!
背景
Google 在最新的 Google I/ O 上推出了 Flutter for Web,旨在进一步解决一次代码,多端运行的问题。Flutter for Web 还处于早期试验版,官方不建议在生产环境上使用。那么到底它的实际情况怎么样呢?我们做了一次预研。期望这次预研的结果可以帮你决定是用,还是不用 FFW。
Flutter for Web 原理
Flutter for Web 和 Flutter 在上层都是 Dart 环境,两者不同的是,Flutter 的 Dart 代码运行在 Dart 虚拟机中,界面由 Flutter 引擎处理,通过 Skia 绘图引擎经由 GPU 绘制到屏幕上。而 Flutter for Web 的 Dart 代码编译成 JavaScript,界面上部分转换成标准的 html 标签,部分转换成通过 Canvas 绘制的自定义标签,最终构成一个 dom 树。
这个原理上的差异非常重要,这直接可以让我们通过原理得出下面的结论:
Flutter for Web 的一致性和体验上存在矛盾
如果 Flutter for Web 追求(和 Flutter)完美的一致性,势必需要大量使用 Canvas 去绘制,而 Canvas 去绘制组件的性能(尤其在移动端)至少不会比 html 标签好。如果 FFW 追求性能极限而使用大量标准的 html 标签,这就会带来和 Weex、RN 等一样的一致性问题:对于 Flutter 所有的控件都是一套代码在绘图引擎上绘制,对 Flutter for Web 如果要使用大量 html 标签,那如何保证一致性呢?只能靠大量精细的打磨工作了。所以 FFW 必须要处理好这个平衡。
为啥使用 canvas 绘制性能不优于手写 html 呢,定性的从几个角度分析:
- FFW 在 canvas 上绘制的组件带有很多 MD 特色的视觉和动画,比如阴影、Z 轴变化等,这部分对性能的消耗要大于普通 html 标签
- FFW 是通过 Dart 的 DSL 转成的 dom 树结构,转化后的 dom 树十分复杂,不太可能比手写的 dom 树更简洁
- 使用 canvas 的控件,其手势事件的捕获分发都是靠 FFW 框架自己实现的,emmmm
虽然不排除 Google 大力出奇迹的情况,但是不管怎样,相同素质的开发人员,相同的界面,性能上也不可能优于 html+css+js
另外一点,如果 FFW 在原理上涉及大量 HTML 标签的转化,那就势必会涉及到碎片化的处理中,浏览器的碎片化程度可一点都不比 Android 系统的碎片化小。像 Flutter 本身之所以被那么多人看重,就是因为其通过绘图引擎这一层,完美的避开了碎片化,保证一致性。
所以最好的平衡就是,只有有限的一部分标准的 html 标签可以被 FFW 复用,其必须有几点性质:
- 标签本身的功能简单又直观
- 最好不要有直接图形化的展示,或者只负责简单的图形化展示(比如画方形)
那几个比较典型的标签就是<p>
、<div>
这种了
Flutter 官方就是这么做的,所以我的结论是:
一致性上大体不会存在问题,性能上,FFW 应该不会优于纯手写 html 标签界面。
官方现状 & 建议
根据官网和 Github repo 上的说法,我们整理了一下:
-
Flutter for Web 和 Flutter 目前暂时是两个仓库,官方正在进行合并,没有给出结论。这一点在工程上非常重要,它说明了几个问题:
- 目前官方对 FFW 的成熟度没有信心,同时 FFW 的迭代速度也很快。
- 目前 FFW 和 Flutter 最多保证 API 一样,实现原理差异可能非常大,同时不保证所有控件都已经在 FFW 上实现。
- 官方不建议应用在生产环境
- 目前插件能力十分有限,和系统交互的一些能力缺失,比如拍照等。
- 性能无法保证,运行会慢,可能会有掉帧
- FFW 中针对桌面的 UI 部分没有完成(跟我无线有什么关系?)
- 开发中只能在 Chrome 中调试(又有什么关系?),release 版是可以运行在任意浏览器中(除了 IE,另支持的最低版本存疑)。
实践
对于这么新的东西,官网上的内容的确不多,而且简单来看这些问题好像也没什么,所以对于到底能不能用,我们还是需要抱着吃螃蟹的心态具体进去预研一下,为了尽快弄清,我计划找一个我们 app 已经做好的 flutter 页面,把它迁移成 FFW,对整个迁移过程做个评估,再看下页面效果,基本上就能得出结论了。
具体的迁移细节就不提了,官网也有迁移文档,大体上就是这么几个步骤
- 安装 Flutter for Web 的工具 webdev
- 改 SDK 依赖,新增 Web 文件夹(和之前存在的 android、ios 文件夹同级),新增一些其他文件(index.html, main.dart 等)。
- 将所有 flutter 代码中依赖的 flutter 包,改成 flutter_web 包
- 去掉所有不兼容的代码,比如多语言、路由、
Platform.isAndroid
等等 - 编译运行
实践的主要目的,有以下几个:
- 对整体坑的深度和广度有个认知,方便推算出填坑成本
- 对 FFW 整体的性能和体验有个把握,尤其是我们自己的页面跑在 FFW 上是什么体验。
- 对 FFW 和 JS 相互调用有具体的了解,如果可行,那复用集团已有能力(比如 mtop)的坑就会小很多
删了一万行代码跑成功之后,最终在工程、开发体验、用户体验上得到一些结论,以下的结论中,体验部分是我在我的 mix2s 上的感受:
工程
-
支持 debug 和 release 模式,后者比前者性能高(差异很明显)。
- debug 模式支持代码修改后自动重新编译,和其他的前端框架(我只用过 Django)一致
- 支持 hotreload,暂时没有尝试
- 支持
webdev build
命令编译出 index.html+js,可以通过 nginx 做反向代理。 -
非常重要 编译出来的代码,gzip 压缩前,最简单的 helloworld 的
main.dart.js
大小约为 500k 左右,sample 中的 gallery 大小约为 2M,阉割版的纯展示用的订单列表大小约为 1.3M。gzip 对文本的压缩率一般是 80%,压缩后也要动辄几百 k 的大小。而且main.dart.js
不加载完,界面是不会展示的。
开发体验
-
非常重要flutter for web 使用 flutter_web 库,且不支持其他许多插件,这会带来几个问题
- 工程上无法优雅的解决 flutter 和 flutter_web 共存的情况,最多搞个 dart2 的 conditional import(这个特性可不在官方文档中哦)
- 依赖 flutter sdk 的几个库,尤其是多语言库无法应用在 flutter_web 上,并且 Google 肯定不会再单独为 flutter_web 适配,而是在合并时做支持。这就意味着现阶段所有依赖 flutter sdk 的库不能被 flutter_web 使用。
-
调试困难
- 错误日志可以打印到浏览器的 console 中,但是 try catch 部分的堆栈不好拿
- 浏览器中的堆栈很复杂,但是基本上能找到出错的 dart 代码
- 目前没有发现单步调试的能力
-
Platform.isAndroid
全部报错,针对 Android 和 iOS 做差异化展示目前还不知道有没有其他方法可以做到。 - 目前没有发现控件的 api 不一致的地方,不过有些控件的行为十分异常,比如下拉刷新,在 Android 手机上经常卡死、失效。猜测重交互的一些控件都有可能存在类似的问题,但是测一遍的成本太高。
- 图片控件
NetworkImage
可以直接用,但是现在来看有些糊,不确定是官方控件的问题,还是我们做的 cdn url 策略有问题。 - dart 代码可以调用 js,开发体验和反射类似,并且需要处理 JS 类型和 Dart 类型的转换。Dart 和 JS 的交互速度未知。只能说哪怕 FFW 有很多浏览器的 API 不支持,也可以通过 JS 来扩展能力。
-
Dart 和 Js 语言本身的差异会带来测试和兼容成本的增加,虽然 Dart 可以编译成 Js,但是跑在 DartVM 上的 Dart 的表现,和其编译成 Js 运行在浏览器中的表现并不完全相同。比如对于如下代码
Map<String, String> query = null; val b = query["abc"];
在 DartVM 中该代码可以执行,结果是
b=null
;但是编译成 Js 以后运行时因为 query 为 null,会报空指针。
用户体验
使用了 chrome、uc、小米自带浏览器分别试了一下订单列表和官方的 sample-gallery 界面,体验如下:
- 加载速度差不多,因为是局域网环境,感知不到差异。
-
非常重要 打出的唯一的
main.dart.js
和部分资源文件(比如MaterialIcons
)没有加载出来的时候,界面不会展示。 - 帧率或者流畅度上,chrome > uc >> 小米自带浏览器,其中 chrome 算是最流畅的体验,但是 sample 中的一些动画和页面转场也可以看到明显的卡顿。
- 文字展示上,看 flutter for web 的界面,chrome 对大部分文字处理的很清晰,小米自带浏览器看文字明显模糊。对比下面两张图,点开放大之后查看,FFW 页面的文字模糊的很明显。
- 所有浏览器都不能选中文字复制粘贴。flutter for web 应该是以 canvas 处理展示文字的,这样才能解释为啥有的字体 size 会模糊,并且不能选中文字。
-
iOS 的 safari 和 chrome 访问 demo 页,TextField 整个不可用,包括以下问题:
- 软键盘弹出逻辑诡异,大部分时候弹出自动缩回,小部分时候正常弹出(Android 这部分表现正常);但焦点转移的时候不会自动收回
- safari 不能输入文字,chrome 可以输入(Android 这部分表现正常)
- 可以选中文字,复制和粘贴不生效(Android 问题相同)
- 焦点到 TextField 的时候界面会自动放大,这个时候很难缩放回去(Android 这部分表现正常)
总结
按照上面的,整体总结一下,Flutter for Web 有几个比较严重的问题,不解决的话估计是无法应用到生产环境上的:
-
包大小问题,这会带来几个问题:
- FFW 的包远大于正常 h5 的包,对流量和页面加载速度都是很大的挑战
- FFW 打成一个 JS 包,多个 FFW 页面无法对公共组件进行复用,进一步造成浪费。
- FFW 的 js 包不加载完,页面无法展示,用户体验极差;而 H5 可以渐进加载,js 可以后入场。
-
SDK 分离的问题,这也会带来几个问题:
- 工程上,很难优雅的解决两个 SDK 并存的问题
- 能力上,依赖 Flutter SDK 的官方库,比如多语言库,不支持 FFW SDK。只能自研一套多语言方案。
- 表单场景不要用 FFW,上述说到的 TextField 的问题不知能否在应用层去解。
- 一些重交互的组件,比如下拉刷新等,存在问题,几乎无法使用。不确定整体组件的质量情况如何,挨个去看成本太大。
结论
- 没有非常强的业务诉求或者技术推动,不要在目前尝试在生产环境使用 Flutter for Web。
- 如果有填坑的决心,并且舍得投入,并且对包大小不关心,对帧率等用户体验也不看重,可以考虑现阶段进行尝试。
- 我个人判断填坑成本在 100 人日以上(上限未知),并且有些坑(包大小)可能根本填不了。
- 什么时候可以再次跟进?我认为在 FFW 合并进 Flutter SDK 的时候,至于他们具体的规划需要问下 Google 的人了。
填坑指北
本节的主要目的是列出假设要做 FFW,我们需要做的技术项和对应方案。
本文作者:shoulder
阅读原文
本文为云栖社区原创内容,未经允许不得转载。