本文介绍了关于“Flutter和Web生态是如何连接的”的知识。很多人在实际案例的操作中会遇到这样的困难。接下来,让边肖带领大家学习如何应对这些情况!希望大家认真阅读,学点东西!
让我们首先讨论为什么Flutter应该连接到网络生态系统。
Flutter现在是热门的跨平台技术,可以在安卓、iOS、PC、IoT和浏览器上运行一组代码,被认为是下一代跨平台技术。与Weex和React Native相比,可以很好地解决多平台一致性问题。本机渲染性能相似。上层没有JS那么厚,整体性能会稍微好一点。
但大多数渴望学习Flutter的人首先怀疑的问题是:Flutter为什么要用Dart?全新的语言意味着新的学习成本。JS不香吗?JS不香,不是有TypeScript吗?其实Flutter抛弃的不仅仅是JS语言,还有HTML和CSS,设计了一个解耦更好的Widget系统。Flutter抛弃了整个Web,致力于创造一个新的生态,但是这个生态不能重用Web生态的代码和解决方案。尤其是之前所有的跨平台解决方案,Hybrid、React Native、Weex都是连接到Web生态系统的,这让Flutter显得格格不入,让大多数前端开发人员望而却步。
以下是我为前端开发人员整理的使用Flutter的成本:
因为Flutter的开发模式与前端框架类似(可以说是复制到React),框架的学习成本不高,但是Dart语言的学习成本略高。除此之外,我们还要学习如何用小部件组装UI。虽然很多布局小部件都是像CSS一样设计的,但是灵活性还是差很多。要想在实际项目中使用,需要对整个工具链进行改造,从“Native First”的角度进行开发。开发Flutter和原生应用的链接类似,但是和前端页面有很大的不同。最高的是生态成本。前端生态的积累,无论是代码还是技术方案,都很难复用。这是最痛苦的一点,生态也是Flutter最薄弱的一环。
不管是为了先进的技术还是商业自私,不管Flutter为什么抛弃Web生态,真正的问题是最大的UI开发者群体是前端,最丰富的生态是Web生态。我认为Web技术也是开发UI最高效的方式。如果能在上层使用Web技术栈开发,在下层使用Flutter实现跨平台渲染,岂不是很好的平衡了开发效率、性能和跨平台一致性?它还可以重用Web技术栈的大量技术积累。
也许这些理由还不够。暂时还是按照这个假设继续分析,然后再讨论对接到底要不要做。
Flutter与Web生态的对接涉及两个方面:
从网络到颤振。就是利用Web技术栈进行开发,然后连接到Flutter实现跨平台渲染。对于Web来说,是解决性能和跨平台一致性的问题,对于Flutter来说,是解决生态复用的问题。
从飘动到网络。是官方实现的对Flutter的Web支持,将用Dart开发的App编译成HTML/JS/CSS后在浏览器上运行,可用于降级和出站场景。
如何实现“从网络到颤振”?
首先,分析Flutter的架构图,看看从哪里开始。
颤振可以分为两部分:框架和发动机。发动机部分与底部相比相对稳定。最好不要动。需要改变的是Dart实现的框架。要想和Web生态系统连接,必须引入JS引擎。是否保留Dart VM还有待讨论。图上方的Material和Cupertino两个UI库的前端是不需要的,前端有自己的。小部件的关键部分是通过用HTML/CSS替换来编写UI,还是保留小部件但将语言改为JS。不同的解决方案是不同的。
实现对接的方案很多,行业内也有很多尝试。我总结了以下三种方式:
TS魔改:用JS引擎代替Dart VM,用JS/TS(或者直接dart2js nbs)重新实现Flutter Framework
p;编译过来)。
JS 对接:引入 JS 引擎同时保留 Dart VM,用前端框架对接 Flutter Framework。
C++ 魔改:用 JS 引擎替换掉 Dart VM,用 C++ 重新实现 Flutter Framework。
TS 魔改
TS 魔改就是完全抛弃掉 Dart VM,用 TypeScript 重新实现一遍用 Dart 写的 Flutter Framework。
为啥是 TS 而不是 JS?这不是因为 TS 是个大热门嘛,而且向下兼容 JS,现在几乎所有时髦的框架都要用 TS 重写了。
这种方案的出发点是“如果能把 Flutter 的 Dart 换成 JS 就好了”,最容易想到的路就是把 Dart 翻译成 TS,或者直接用 dart2js 把代码编译成 js,但是编译出来的代码包含很多 dart:ui 之类的库的封装,生成的包也挺大的,也比较难定制需要导出的接口,不如干脆用 TS 重写一遍,工具链更熟悉一些,还可以加一些定制。
理论上讲翻译之后 Flutter 绝大部分功能都依然支持,可以复用各种 npm 包,还可以动态化,但是丧失了 AOT 能力,JS 语言的执行性能应该是不如 Dart 的。而且所有节点的布局运算都发生在 JS,底层只需要提供基础的图形能力就好了,就好像是基于 Canvas API 写了一套 UI 框架,性能未必有现存前端框架的性能高。
此外最大的问题是如何与官方 Flutter 保持一致,假如现在是从 v1.13 版本翻译过来的,以后官方升级到了 v1.15 要不要同步更新?这个过程没啥技术含量,而且需要持续投入,做起来比较恶心。
另外还需要考虑上层是用 Widget 的方式写 UI,还是用前端熟悉的 HTML+CSS。如果依然用 Widget 的话,那大部分前端组件还是用不了的,UI 还是得重写一遍。反正要重写的话,成本也没降下来,那就用 Dart 重写呗…… 直接用官方原版 Flutter 也避免每次更新都要翻译一遍 Dart 代码。所以既然选择了对接前端生态,那就要对接 CSS,不然就没有足够的价值。然而 CSS 和 Widget 的对接也是很繁琐的过程,而且存在完备性问题。
JS 对接
翻译代码的方式不够优雅,那就保留 Dart,把 JS/CSS 对接到 Widget 上面不就好了?
当然可以,这种方式是仅把 Flutter 当做了底层的渲染引擎,上层保持前端框架的写法,仅把渲染部分对接到 Flutter。现存的很多前端框架都把底层渲染能力做了抽象,可以对接到不同渲染引擎上,如 Vue/Rax 同时支持浏览器和 Weex,用同样的方式,可以再支持一个 Flutter。
这种方式对前端框架的兼容性比较好,但是链路太长了,业务代码调用前端框架接口做渲染,一顿操作之后发出了渲染指令,这个渲染指令要基于通信的方式传给 Flutter Framework,这中间涉及一次 JS 到 C++ 再到 Dart 的跨语言转换,然后再接收到渲染指令之后还要转成相应的 Widget 树,从 CSS 到 Widget 的转换依然很繁琐。而且 Widget 本身是可以带有状态的,本身就是响应式更新的,在更新时会重新生成 widget 并 diff,如果在前端更新 UI 的话,前端框架在 js 里 diff 一次 vdom,传到 Flutter 之后又 diff 一次 widget。
如果要绕过 Widget 直接对接图中的 Rendering 这一层,可以绕过 widget diff 但是得改 Flutter Framework 的渲染链路,既然要改 Flutter Framework 那为什么不直接用 TS 魔改呢,还绕过了 JS 到 Dart 的通信,又回到了第一种方案。
总结来说,这个方案的优点是:实现简单、能最大化保留前端开发体验,缺点是:渲染链路长、通信成本高、响应式逻辑冲突、CSS 转 Widget 不完备等。
C++ 魔改
想要干掉 Dart VM,就需要用其他语言重新实现用 Dart 开发的 Framework,用 JS/TS 可以,用 C++ 当然可以,最硬核的方式就是用 C++ 重新实现 Flutter 的 Framework,然后接入 JS 引擎,通过 binding 把 C++ 接口透出到 JS 环境,上层应用还是用 JS 做开发。
把 Framework 层下沉到 C++ 之后,不仅会有更好的性能,也能支持更多语言。原本 Flutter Framework 是在 Dart VM 之上的,必须依赖 Dart VM 才能运行,所以对 Dart 有强依赖;用 C++ 重新实现之后,JS 引擎是在 C++ 版 Framework 之上的,框架本身并不依赖 JS 引擎,还可以对接其他各种语言,如对接了 JVM 之后可以支持 Java 和 Kotlin,对接回 Dart VM 可以继续支持 Dart。
这个方案可以增强性能,也能保持和 Flutter 的一致性,但是改造成本和维护成本都相当高。C++ 的开发效率肯定不如 Dart,当 Flutter 快速迭代之后如何跟进是很大的问题,如果跟进不及时或者实现不一致那很可能就分化了。从 CSS 到 Widget 的转换也是不得不面对的问题。
几种方案对比
把上面几种方案画在同一张图里是这个样子的:
图中实线部分表示了跨语言的通信,太过频繁会影响性能,虚线部分表示了其他对接可能性。
从下到上,Flutter Engine 是不需要动的,这一层是跨平台的关键。Framework 则有三种语言版本,JS/TS、Dart、C++,性能是 C++ 版本最好,成本是 Dart 版本最低。然后还需要向上处理 HTML/CSS 和 Widget 的问题,可以直接对接一个前端框架,也可以直接在 C++ 层实现(不然需要透出的 binding 接口就太多了,用通信的方式也太过频繁了)。
如何实现“从 Flutter 到 Web”?
这个功能官方已经实现了,可以把使用 Dart 开发的 App 编译成 Web App 运行在浏览器上,官方文档以介绍用法和 API 为主,我这里简单分析一下内部具体的实现方案。
实现原理
结合 Flutter 的架构图来看,要实现 Web 到 Flutter 需要改造的是上层 Framework,要实现 Flutter 到 Web 需要改造的则是底层 Engine。
Framework 对 Engine 的核心依赖是 dart:ui,这是库是在 Engine 里实现的,抽象出了绘制 UI 图层的接口,底层对接 skia 的实现,向上透出 Dart 语言的接口。这样来看,对接方式就比较简单了:
-
使用 dart2js 把 Framework 编译成 JS 代码。
-
基于浏览器的 API 重新实现 dart:ui,即 dart:web_ui。
把 Dart 编译成 JS 没什么问题,性能可能会有一点影响,功能都是可以完全保留的,关键是 dart:web_ui 的实现。在原生 Engine 中,dart:ui 依赖 skia 透出的 SkCanvas 实现绘制,这是一套很底层的图形接口,只定义了画线、画多边形、贴图之类的底层能力,用浏览器接口实现这一套接口还是很有挑战的。上图可以看到 Web 版 Engine 是基于 DOM 和 Canvas 实现的,底层定义了 DomCanvas 和 BitmapCanvas 两种图形接口,会把传来的 layer tree 渲染成浏览器的 Element tree,但是节点上仅包含了 position, transform, opacity 之类的样式,只用到 CSS 很小的一个子集,一些更复杂的绘制直接用2D <canvas> 实现。
存在的问题
我编译了一个还算复杂的 demo 试了一下,性能很不理想,滑动不流畅,有时候图片还会闪动。生成出来的 js 代码有 1.1MB (minify 之后,未 gzip),节点层次也比较深,我评估这个页面用前端写不会超过 300KB,节点数可以少一半以上。
另外再看一下 Flutter 仓库的 issue,过滤出 platfrom-web 相关的,可以看到大量:文字编辑失效、找不到光标、ListView 在 ios 上不可滚动、checkbox/button 行为不正常、安卓滚动卡顿图片闪烁、字体失效、某些机型视频无法播放、文字选中后无法复制、无法调试…… 感觉 flutter for web 已经陷入泥潭,让人回想起前端当年处理各种浏览器兼容性的噩梦。
这些性能和兼容性问题,核心原因是浏览器未暴露足够的底层能力,以及浏览器处理手势、用户输入和方式和 Flutter 差异巨大。
实现 Flutter Engine 需要的是底层的图形接口和系统能力,虽然 <canvas> 提供了相似的图形接口,如果全部用 canvas 实现的话很难处理可访问性、文本选择、手势、表单等问题,也会存在很多兼容性问题。所以真实方案里用的是 Canvas + DOM 混合的方式,封装层次太高了,渲染链路太长。就好像 Flutter Framework 里进行了一顿猛如虎的操作之后,节点生成好了、布局算好了、绘制属性也处理好了,就差一个画布画出来了,然后交到浏览器手里,又生成一遍 Element,再算一遍布局,在处理一遍绘制,最终才交给了底层的图形库画出来。
再比如长页面的滚动,浏览器里只要一条 CSS (overflow:scroll) 就可以让元素可滚动,手势的监听以及页面的滚动以及滚动动画都是浏览器原生实现的,不需要与 JS 交互,甚至不需要重新 layout 和 paint,只需要 compositing。如上图所示,在 Flutter 中 Animation 和 Gesture 是用 Dart 实现的,编译过来就是 JS 实现的,浏览器本身并不知道这个元素是否可滚,只是不断派发 touchmove 事件,JS 根据事件属性计算节点偏移,然后运算动画,然后把 transform 或者新的 position 作用到节点上,然后浏览器再来一遍完整的渲染流程……
优化方案
性能和兼容性的问题还是要解决的,短期内先把 issue 解掉,长线的优化方案,官方有两种尝试:
使用 CSS Painting API 做绘制。
-
这是还处于提案状态的新标准,可以用 JS 实现一些绘制功能,自定义 CSS 属性。
-
目前还未实现,需要等浏览器先把 CSS Houdini 支持好。
使用 WebAssembly 版本的 Skia 做绘制。https://skia.org/user/modules/canvaskit
-
这样可以发挥 wasm 的性能优势,并且保持 skia 功能的一致。但是目前 wasm 在浏览器环境里未必有性能优势,这里不展开讨论了。
-
已经部分实现,参考这里的配置启用功能: https://github.com/flutter/flutter/issues/41062#issuecomment-533952994
这两个方案都是想更多的利用到浏览器的底层能力,只有浏览器暴露了更多底层能力,才能更好的实现 Flutter 的 Web Engine。不过这个要等挺久的时间,我们也参与不了,现阶段想要使用 flutter for web,还是得保持现有架构,一起参与进去把 issue 解决掉,优先保障功能,其次优化性能。
一种适应性更好的架构
如果理想化一点,能不能从架构角度让 Flutter 和 Web 生态融合的更好一些呢?
回顾文章最开始的官方架构图,上面是 Framework(Dart),下面是 Engine(C++),切分在 Foundation 这一层,双方之间的交互是几何图形信息。如果还保持这个架构,把切分层次划分的更靠上一些,如下图所示,划分在 Widgets 和 Rendering 这一层,理论上讲对 Flutter 的开发者来说是无感知的,因为上层的开发语言和 Widget 接口都是不变的。
切分在这一层,Framework 和 Engine 之间的交互就不再是几何图形而是节点信息,Widget 的组合、setState 响应式更新、Widget diff 都还在 Dart 中,展开后的 RenderObject 的布局、绘制、裁剪、动画全都在 C++ 中,不仅有更好的性能,还可以与 Engine 有更好的结合。
或者说,还原本保留 Engine 的设计,把下沉的这部分逻辑上划分成 Renderer,就有了如下三层的结构:
这样划分出来的每一层都有明确的定位:
-
Framework: 开发框架。为开发者提供可编程 API,实现响应式的开发模式,提供细粒度 Widget 供开发者自由封装和组合。
-
Renderer: 渲染引擎。专门实现布局、绘制、动画、手势的的处理,这部分功能相对独立,是可以与开发框架解耦的,也不必与特定语言绑定。
-
Engine: 图形引擎。实现跨平台一致的图形接口,合成输入的层并绘制到屏幕上,处理好平台力的接入和适配。
这样切分除了有性能优势以外,也使得渲染引擎摆脱了对 Dart 的依赖,能够支持多种语言,也能支持多种开发模式。对接到 Dart VM 就可以用 Dart 写代码,对接到 JS 引擎就可以用 JS 写代码,对接到 JVM 还可以写 Java,但是无论怎么写,底层的渲染能力是一样的,一套统一的布局算法,动画和手势的处理行为也是一致的。
在这样的架构下,对接 Web 生态就更容易了。Dart 和 Widget 是前端不想要的,希望能换成 JS 和 CSS,但是又想要底层的跨平台一致渲染引擎,那从 Renderer 层开始对接就好了,绕过了所有不想要的,也保留了所有想要的。
要实现 Flutter for Web 也更简单了一些。在 Engine 层做对接,一直苦于浏览器透出的底层能力不够,如果是在 Renderer 之上做对接就更容易一些,基于 JS/CSS/DOM/Canvas 的能力封装出一套 Rendering 接口,供 Widget 调用就好了,这样可以使渲染链路更短一些,但是依然要处理 Widget 和 DOM/CSS 之间的兼容性问题。
再讨论一遍:为什么要对接?
技术上已经分析完了,要想搞定 Flutter 生态和 Web 生态的对接,需要投入很大的成本,所以真正决定做之前,要先讨论清楚为什么要做对接?到底要不要做对接?
首先 Google 官方对 Flutter 的定位就是个问题。Flutter 设计之初就是不考虑 Web 生态的,甚至在刻意回避,倡导的是更贴近原生的开发方式。我之所以在开头说不要对接,原因也很简单:两种技术设计理念不同,不是朝着一个方向发展的,生态不通,技术方案不通,强行融合很可能让彼此都丧失了优势。但是业界又有很多团队在做这种尝试,说明需求是存在的,如果 Google 抵制这个方向,那就不好做了。不过现在官方已经支持了 Flutter for Web,已经向 Web 生态迈了一步,未来是否进一步与 Web 融合,也是有可能的。
另外就是跨平台技术本身的问题,浏览器发展了二三十年,已经是个很强大的跨平台产品了,几乎是 Web 的代名词了,这一点无人能敌。但是也臃肿不堪,有大量历史包袱,性能和体验不够好,和 Native 的结合度差,尤其在移动和 IoT 平台。虽然硬件性能在不断提升,但这是所有软件共享的,浏览器的性能和体验总会比 Native 差一些,差的这一些很可能就是新业务和新场景的发挥空间。观察一下近几年新诞生的业务场景,很多都是利用到了 Native 新提供的能力才火爆起来的,如 AI/AR/视频/直播 等,有因为新的 Web API 而孵化生出来的商业模式吗?
“Flutter和Web生态是怎么对接的”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/105864.html