[AliFlutter]Flutter for Web在无影中的应用

本文涉及的产品
无影云电脑企业版,4核8GB 120小时 1个月
无影云电脑企业版,4核8GB 120小时 1个月
无影云电脑个人版,黄金款:40核时/1个月有效
简介: 无影是使用Flutter的重度用户,无论是在成熟的移动Android、iOS上,还是桌面端MacOS、Windows、还有各种硬件终端上(Linux)上都有应用![](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/c2434612-86ee-4fb6-a7d1-1622eb6d050d.png)今年无影使用Flutt

无影是使用Flutter的重度用户,无论是在成熟的移动Android、iOS上,还是桌面端MacOS、Windows、还有各种硬件终端上(Linux)上都有应用

今年无影使用Flutter的端又新添了一名成员—浏览器。很兴奋的告诉大家,我们已经将Flutter应用到了Flutter能够覆盖的所有场景。

去年11月我开始研究Flutter for Web的可行性,部分内容我不再复述,大家自行查看这篇内容了解),12月初开始,我就投入了大部分精力到无影桌面端转Web上了,到今年1月初,Flutter转Web工程开始提测,2月9日,发布了第一个线上版本(https://wuying.aliyun.com/v3 )。 这篇文章给大家分享我们的改造过程及踩坑过程。

背景

在使用Flutter for Web前,我们无影客户端是桌面端和Web客户端各自开发维护。桌面端采用Flutter开发,Web端使用React开发,同时桌面端和web端的功能也几乎一致。还有,我们无影是一个高速发展的项目,功能迭代很快,每个版本都会新增大量功能以及会有大量功能的重构,这从下面的图就可以看出来。去年7月份我们重构了UI发布了5.0版本,到去年11月的时候,又发布了为云栖打造的新的交互版本。

由于使用两套代码开发,这给我们项目的迭代带来了很大的挑战,我们需要大量人力去做开发,同时还存在两端发布时间间隔较长、功能不一致等问题,而如果能将桌面端代码直接复用到Web端,就能完美解决这些问题,这也促使我们花费更多的精力去研究Flutter for Web。

面临的挑战

虽然Flutter for Web在Flutter2.0版本就已经合入主线,但还是存在很多问题。

我们选择了canvaskit渲染模式,因为html渲染模式还存在一些解不了的问题:

  1. html渲染模式存在很多兼容性问题,部分Flutter API直接无法使用,比如BlendMode.clear,在html渲染模式中使用会直接报错,我们项目中还大量使用了该API,且没有找打其它方式去替代。
  2. html模式渲染效果部分地方同桌面端存在差异
  3. html渲染模式在列表滚动、动画下渲染效果十分糟糕,我们的客户端主页里正好存在这些case(同时存在Flutter布局更新及DOM的布局更新导致渲染性能下降)

因为这些原因,我们放弃了使用html模式,使用了canvaskit模式。不过canvaskit模式也依然存在部分问题

  1. Flutter for Web本身的编译产物就很大,而canvaskit渲染模式还需要额外引入canvaskit.wasm,极大增加了首屏渲染下载资源的大小
  2. canvaskit使用WebGL渲染,WebGL无法加载系统字体,加载额外的字体文件也给首屏渲染增加负担
  3. canvaskit渲染模式使用了WebAssembly和WebGL,所以存在一些兼容性问题。

同时,还面临了一些不可避免的挑战

  1. 基础功能如日志、埋点、存储等需要在Web侧进行支持
  2. 工程化生态不足,构建、部署及静态资源处理都需要从0到1支持
  3. 存在一些dart:html提供的标准API兼容性问题,如退出全屏document.exitFullscreen不是所有浏览器都兼容

功能适配

  1. 桌面端已有功能适配

我们桌面端存在大量插件来扩展Flutter的能力,所以在Web端,我们必须逐一去分析插件的能力然后使用Web的方式去兼容,兼容方式可以看看我上篇文章平台兼容,这里工作量巨大且容易遗漏,只能靠测试来保证功能完整,存在一些风险。

  1. JS侧已有功能适配

集团有很多JavaScript版本的库,在dart中并没有相应的版本,而桌面端使用的是C++实现的,所以需要进行适配。比如我们的埋点库,其架构图如下

我们根据dart版本的能力抽象出tracer_interface层,然后在JS中找到对应能力并抽象出JS_interface层,其用于对接三方库,然后采用Dart调用JavaScript来对接JavaScript的SDK。

首屏加载优化

刚刚说了首屏资源过大造成资源下载时间变长会极大降低用户使用的意愿。经过一系列探索和试错,我总结了以下优化首屏资源的方案。

文字大小处理

canvaskit渲染模式是采用的webgl渲染,webgl渲染无法使用系统字体,必须加载额外的字体文件,我们使用的是阿里普惠体,完整的阿里普惠体有8MB的大小,我们需要优化这个字体的大小。这里我使用了工具fontTools将常用的3000多个字符包括字母及符号输出到一个大约800KB的文件中,然后首屏渲染时只加载这个优化的字体文件,当首屏渲染完成后,再使用Flutter提供的FontLoader去动态加载完整的字体文件。

动态加载字体:

var bundle = await rootBundle.load('assets/fonts/xxx.ttf');
var loader = FontLoader('Alibaba PuHuiTi')..addFont(Future.value(bundle));
await loader.load();

MaterialIcon图标文件处理

由于历史原因,我们项目中使用了部分Material的图标,因此在编译时会将完整的MaterialIcons-Regular.otf文件(1.6MB)放到fonts目录并在首次渲染时加载,万幸Flutter提供了工具(传入--tree-shake-icons),可以将这个文件精简,只包含项目中使用到的图标。然而在Flutter3.0.2版本中编译web时传入这个参数会导致编译错误,最新master分支已支持),我们可以通过flutter build apk --tree-shake-icons编译Android的方式来获得这个优化的MaterialIcons-Regular.otf文件,然后在Web的编译产物中替换这个文件。

使用deferred as拆分文件

Flutter会将整个工程编译到一个文件mian.dart.js文件中,我们可以使用deferred as来拆分文件,将文件拆分为mian.dart.js_1.part.jsmian.dart.js_2.part.js等,这样就减少了首次加载文件的大小。

支持Gzip压缩

Gzip压缩跟项目本身的设置没有关系,它需要网站部署的服务去支持,这个步骤对大文件的优化效果很好。顺便提一下,我们的前端使用的是O2 Space部署,目前它对wasm格式的文件没有支持Gzip压缩,这里我通过将canvaskit.wasm改为canvaskit.txt来支持canvaskit文件的Gzip压缩(希望官方尽快支持)。

使用加载动画

我使用了Flutter提供的加载函数并使用了JavaScript及CSS做了一个加载动画盖在Flutter屏幕上面,当Flutter界面加载完成并显示时,通知JS隐藏加载动画来达到一个无痕切换。

经过这一系列优化后,我们无影Web客户端从刚开始首屏加载需要23MB资源到最后只需要加载6.6MB资源,需要说明的是,我们的项目也提供了静态资源缓存,所以只有用户首次访问我们的网站是需要下载这些资源,后续再次访问时能从缓存中获得更快的加载速度。

其它疑难问题

  1. 后备字体处理

canvaskit模式需要加载字体的特性,让Flutter官方团队为了保证所输入的字体都能正常显示,引入了后备字体的概念,当输入的字符缺少字体引入时,会从google字体库fonts.gstatic.com下加载后备字体。我们在测试中发现,即使我们加载了完整的阿里普惠体文件,Flutter还是会去加载后备字体,而如果下载后备字体网络遇到问题,首屏加载的速度非常慢。后来我测试出是我们的字体库中没有零宽字符(#8203)导致。国内因为一些众所周知的原因,访问fonts.gstatic.com有很大概率出现问题,所以只能修改引擎代码,将Flutter加载后备字体的代码去掉。

// 注释flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart中的resolvedFonts.forEach(notoDownloadQueue.add);
// resolvedFonts.forEach(notoDownloadQueue.add);

然后重新编译flutter engine。

使用自定义Flutter Web Engine的方式:最好的方式当然是搭建Flutter Engine源,不过成本太高。我们目前采用的方式是将重新编译的引擎中的 {engine路径}/src/out/host_debug_unopt/flutter_web_sdk文件夹替换 {flutter路径}/bin/cache/flutter_web_sdk,然后将这个编译后的 flutter_web_sdk压缩后团队内共享。
  1. 去除浏览器默认样式

在Safari、FireFox等浏览器中,会存在一些默认样式,比如密码输入框后面会出现自带的图标

如果我们要去除这种默认样式,直接写外联css不会生效,需要在flt-glass-pane下写css来清除。JS代码如下

const parent = document.querySelector('flt-glass-pane')
const showDom = parent.shadowRoot
let style = document.createElement('style');
style.innerHTML = '::-ms-reveal { display: none; } ';
showDom.appendChild(style);

部署

部署流程

上面提到的我们部署Flutter产物使用的O2 Space,部署流程如下:

我新建了github仓库用来放置Flutter编译的web产物,目录结构大概如下

├─ .gitignore
├─ .npmrc
├─ abc.json # O2 Space部署脚本
├─ gulpfile.js # 针对Flutter产物二次编译
├─ package-lock.json
├─ package.json
└─ web # Flutter构建产物
       ├─ .last_build_id
       ├─ assets
       ├─ entry.json
       ├─ favicon.png
       ├─ flutter.js
       ├─ flutter_service_worker.js
       ├─ icons
       ├─ index.html
       ├─ main.dart.js
       ├─ main.dart.js_1.part.js
       ├─ manifest.json
       ├─ schedule.css
       ├─ schedule.js
       └─ version.json

当执行flutter build web后,将构建的web产物上传到该仓库中,gulpfile.js会对产物二次编译,比如清除代码注释、JS代码混淆等,只要我们配置好abc.json这些事情会在O2 Space部署时自动完成。

使用CDN静态资源

Flutter加载的静态资源默认需要和index.html在同一目录下。我们可以通过下面方式分别设置静态资源和JavaScript

  1. 静态资源(除JavaScript外):我们可以通过在index.html中设置meta为assetBase的标签来改变项目中加载图片、字体、json文件等资源,如<meta name="assetBase" content="https://dev.g.alicdn.com/aliyun/cdn/0.0.1/"/>。这部分可以在gulpfile.js中动态替换以达到开发环境和部署环境区分开。
  2. JavaScript:因为Flutter加载JS使用的appendChild来加载带src的script标签,我们可以通过重载document.body.appendChild函数来达到更改src路径的目的。
let cdnURL = document.querySelector('meta[name="assetBase"]') ? .getAttribute('content') ? ? '/';
let appendChild = document.body.appendChild
document.body.appendChild = function (el) {
  return appendChild.call(document.body, function () {
    {
      if (cdnURL !== '/' && el.nodeName.toLowerCase() === 'script' && el.baseURI !== cdnURL) {
        el.src = el.src.replace(el.baseURI, cdnURL)
      }
      return el
    }
  })
}

效果展示

总结

Flutter for Web对于无影目前的情况来说非常合适,不过还是有一些未解决的问题:

  1. 首屏加载资源依然很大,需要持续优化
  2. 主页长时间静置偶现崩溃,未找到具体原因
  3. 部署流程未全部自动化,有出错风险
  4. 无法方便的使用JS生态,需要手动兼容
  5. 字体问题需要自定义引擎

针对这些问题,我也在我们Flutter for Web工程上提出了一些展望

  1. 将首屏资源加载大小控制在5MB以内
  2. 构建部署docker,将部署流程自动化
  3. 编写工具自动生成JS和Dart接口,方便Dart对接JS生态
  4. 与官方共建,找到更完美使用后备字体的方式
目录
相关文章
|
3天前
|
SQL 缓存 搜索推荐
后端技术在现代Web开发中的应用与挑战
本文将深入探讨后端技术在现代Web开发中的重要性,涵盖从基础架构到性能优化的多个方面。通过分析当前主流后端技术的优缺点,并提供一些实用的解决方案和建议,帮助开发者更好地应对日常开发中的挑战。
17 1
|
1天前
|
开发框架 搜索推荐 开发工具
打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第51天】本文是一篇面向初学者的Flutter入门教程,旨在通过简单易懂的语言和实际代码示例,引导读者步入跨平台移动应用开发的世界。文章首先介绍了Flutter的基本概念和优势,然后逐步展示了如何搭建开发环境、创建第一个Flutter应用,并实现了一个简单的待办事项列表。最后,文章探讨了Flutter在实现高性能和美观界面方面的潜力,鼓励读者发挥创意,探索更多可能。
29 15
|
1天前
|
人工智能 运维 云计算
阿里云无影AI云电脑亮相 体验大幅升级
9月20日,2024云栖大会上阿里云无影AI云电脑全新亮相,基于最新的终端云计算技术和AI大模型能力,无影的综合体验大幅提升,新增了弹性升降配、双网自由切换、多端操作系统知识库问答、编码大师等AI智能体功能,为安全办公、个人娱乐带来全新的云上流畅体验,更可畅玩《黑神话:悟空》等3A游戏大作。同时,无影还宣布向开发者全面开放应用中心生态,开发者可免费入驻。
|
8天前
|
JSON Dart Java
flutter开发多端平台应用的探索
flutter开发多端平台应用的探索
20 6
|
8天前
|
JSON Dart Java
flutter开发多端平台应用的探索 下 (跨模块、跨语言通信之平台通道)
flutter开发多端平台应用的探索 下 (跨模块、跨语言通信之平台通道)
|
8天前
|
人工智能 关系型数据库 数据安全/隐私保护
后端技术在现代Web开发中的应用与挑战
本文将深入探讨后端技术在现代Web开发中的重要性,通过分析其在数据处理、业务逻辑实现和安全性保障方面的应用,揭示后端技术的核心价值。同时,本文还将讨论当前后端开发面临的主要挑战,如高并发处理、数据安全、微服务架构的复杂性等,并给出相应的解决方案。无论是后端开发者还是对后端技术感兴趣的读者,都可以通过这篇文章获得启发和指导。
|
16天前
|
前端开发 安全 JavaScript
构建高效Web应用:前后端分离架构的实践
【9月更文挑战第4天】在数字时代,Web应用已成为企业与用户互动的主要平台。本文将介绍如何通过前后端分离的架构设计来构建高效的Web应用,探讨该架构的优势,并分享实现过程中的关键步骤和注意事项。文章旨在为开发者提供一种清晰、高效的开发模式,帮助其在快速变化的市场环境中保持竞争力。
|
20天前
|
中间件 编译器 数据处理
在web开发中应用管道过滤器
【9月更文挑战第1天】本文介绍管道-过滤器架构将数据处理流程分解为一系列独立组件,通过管道连接,适用于数据流处理如图像处理、编译器设计等。通过具体实例说明了Gin如何有效支持管道-过滤器风格的设计,构建高性能Web服务。
33 9
|
18天前
|
编解码 Dart 网络协议
Flutter如何玩转超低延迟RTSP/RTMP播放,跨平台视频流体验大升级,让你的应用秒变直播神器!
【9月更文挑战第3天】Flutter作为谷歌推出的跨平台移动UI框架,凭借高性能和丰富的生态系统广受好评。本文详细介绍如何在Flutter应用中实现低延迟的跨平台RTSP/RTMP播放,并提供具体示例代码。首先介绍了如何使用`flutter_vlc_player`播放RTSP流,然后讨论了优化视频播放以降低延迟的方法,包括调整播放器配置等。通过选用合适的播放器插件并进行优化,Flutter可在视频流播放领域提供卓越的用户体验。随着生态的发展,Flutter有望成为视频流媒体开发的首选框架。
73 6
|
17天前
|
Dart 搜索推荐 API
打造个性化天气应用:从零开始的Flutter之旅
【9月更文挑战第3天】探索Flutter的强大功能,我们将一步步构建一个动态的天气应用。通过这个实践项目,你将学习到如何从无到有地设计用户界面、处理数据流和集成第三方API。本指南适合所有水平的开发者,无论你是Flutter新手还是寻求提高的资深开发者,都能在这里找到价值。让我们开始吧,创造属于你的天气小助手!

相关产品

  • 无影云电脑