FlutterWeb启动耗时优化

Flutter Web 启动性能优化实践

Flutter Web 性能优化

优化效果展示

本文字数:4307
预计阅读时间:30 分钟

01 前言

在之前的文章中,我们详细介绍过 Flutter Web 从0到部署上线的流程,并且针对其中遇到的问题进行了分析与修复。我们也对 Flutter Web 的项目进行了打包部署上线。在产品体验中会发现,跟传统的 web 端网页对比,还是明显感觉到性能上的差异,特别是启动的过程明显耗时较长。本文将针对 Flutter Web 项目做一些启动上的优化和探讨。SDK 版本:Flutter SDK 3.19.0。

02 main.dart.js 分包

main.dart.js是 Flutter Web 的编译产物,所有的业务逻辑都在这一个 js 文件里,可想而知它会比较臃肿。在 Flutter Web 发布后,就有了 main.dart.js 分包的需求,使用 deferred-components 即可达成分包的目的。

1、添加依赖

通过将 deferred-components 依赖添加到应用程序的 pubspec.yaml 中的 flutter 下即可:

flutter:
  deferred-components:

2、实现

我们找到一个可以被延迟加载的 Widget,使用 deferred 关键字进行导入:

import './widget/me_read_achievement_widget.dart' deferred as achievement;

然后调用 loadLibrary()

late Future<void> _libraryFuture;


void initState() {
  super.initState();
  _libraryFuture = achievement.loadLibrary();
}

最后使用 FutureBuilder 等待 loadLibraryFuture 对象初始化完成,并实现延迟加载:


Widget build(BuildContext context) {
  return FutureBuilder<void>(
    future: _libraryFuture,
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.done) {
        return achievement.MeReadAchievementWidget();
      }
      return const CircularProgressIndicator();
    },
  );
}

3、编译产物

打包完成后我们看下编译产物,会发现除了 main.dart.js 之外又多了一个 main.dart.js_1.part.js

分包编译产物

main.dart.js_1.part.js 即为分包。

4、分包加载

部署之后我们打开浏览器看一下效果:

在启动 Web 页面后我们发现只加载了 main.dart.js
初始加载

只有操作到相应的页面,才会加载 main.dart.js_1.part.js
延迟加载

这说明如果业务比较复杂页面很多的情况下,我们可以根据自己的需求和体验细化项目的结构,将 Widget 进行拆分,在需要的时候再进行延迟加载,一定程度上减少 main.dart.js 的包体积,以达到启动时加速的目的。

小技巧:使用 Appuploader 可以更方便地管理和优化 Flutter Web 项目的构建产物,它提供了可视化的分包管理功能,让优化过程更加直观高效。

03 canvaskit.js 和 canvaskit.wasm

CanvasKit 是以 WASM 为编译目标的 Web 平台图形绘制接口,其目标是将 Skia 的图形 API 导出到 Web 平台。其中 canvaskit 中绘制的实现全部在 canvaskit.js 中调用浏览器绘制 API 来实现,而计算相关的内容全部放在了 wasm 中实现。由此可知这两个文件是实现 Flutter Web 渲染核心文件。它们也是在启动过程中进行的,且是从 google 的服务器进行的下载:

canvaskit.js:
canvaskit.js 加载

canvaskit.wasm:
canvaskit.wasm 加载

由于众所周知的原因,访问这两个地址将会比较慢,在有些时候甚至会访问失败。这两个文件的加载失败也就直接导致了页面无法渲然的问题。由于我们的项目是 CDN 部署,因此我们也需要把它部署到 CDN 上面,并访问其 CDN 地址。官方给了两种方式:

1、在初始化引擎时配置

初始化配置

从官网说明来看,我们可以在 initializeEngine 时通过配置 canvasKitBaseUrl 参数的方式来实现加载 canvaskit 相关的文件。如:

_flutter.loader.loadEntrypoint({
  onEntrypointLoaded: async function(engineInitializer) {
    let appRunner = await engineInitializer.initializeEngine({
      canvasKitBaseUrl: YOUR_CANVASKIT_CDN_BASE_URL
    });
    await appRunner.runApp();
  }
});

其中 YOUR_CANVASKIT_CDN_BASE_URL 是你 canvaskit.jscanvaskit.wasm 文件所在的根目录,然后正常编译打包即可。另外我们也可以不改源文件配置,通过在打包时配置参数来实现。

2、打包配置参数

我们可以通过以下参数进行配置:

--dart-define=FLUTTER_WEB_CANVASKIT_URL

完整的打包命令如下:

flutter build web -t lib/main_test.dart --no-tree-shake-icons --dart-define=FLUTTER_WEB_CANVASKIT_URL=YOUR_CANVASKIT_CDN_BASE_URL --release

通过实践,这两种方式都可以实现从自己的 CDN 上读取 canvaskit.jscanvaskit.wasm 文件的目的,我们来看一下执行结果:
优化后加载
优化后效果

对比一下,从 Google 服务器下载下来的用时:
Google 服务器加载
Google 服务器耗时

都用了2,3秒的时间,而从我们自己的 CDN 上:
CDN 加载
CDN 耗时

只用了毫秒级,优化效果非常明显。

注意:在开发调试的过程中,由于直接加载的是本地的 canvaskit.jscanvaskit.wasm 文件,所以不用做任何处理。

04 字体加载优化

优化了 canvaskit 相关文件后,我们发现启动还是很慢,抓包看原来还有一些字体是从 Google 下载的,比如:
字体加载
字体文件

这两个文件的下载路径修改,Flutter 官方并没有给任何参考。那么我们找找加载它们的实现。打开编译后的产物,main.dart.js,我们搜一下这两个字体:
字体实现

找到了这个代码后,处理就很简单了,将 KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf 这个文件下载下来,放到 CDN 上,再修改一下这段代码的 url 到我们的 CDN 地址即可。另一个字体我们看到,它是在一个数组中:
字体数组

这个数组长度非常长,有一百多个成员。Flutter 会根据不同的浏览器环境,和不同的语言环境加载不同的字体。其 Host 定义如下:
Host 定义

其中 j 就是每个字体的 url 参数。一百多个字体一个一个手动下载是不太现实了,但我们可以批量下载,思路就是从这个数组中截取所有的 ttf 路径,拼上 Host 进行下载。注意:需要保留 Google 的目录结构。大家可以用任意自己熟悉的方式实现批量下载,我用 Python 写了个脚本进行批量实现:

# coding=utf-8
import os
import requests

original_text = '...' # 原始文本内容
font_list_content = original_text.split('"')
font_list = []
for font in font_list_content:
    if font.endswith(".ttf"):
        font_list.append(font)

host = 'https://fonts.gstatic.com/s/'

fonts = "fonts/"
path = ""
for font in font_list:
    folder_list = font.split("/")
    for folder in folder_list:
        if (folder.endswith(".ttf")):
            url = host + path + folder
            proxies = {
                'http': 'http://127.0.0.1:9098',  # 替换为你的VPN IP和端口
                'https': 'http://127.0.0.1:9098',  # 同上
            }
            r = requests.get(url,proxies=proxies)
            print("finish")
            with open(fonts + path + folder, 'wb') as file:
                file.write(r.content)
                path = ""
        else:
            path += folder + "/"
            os.makedirs(fonts + path, exist_ok=True)

其中由于这两天 Google 的字体服务器连接不上,所以我使用了 vpn,在代码中需要通过 proxies 进行代理的相关配置。下载完成后的结果如下:
下载结果

我们再将所有的字体资源上传到 CDN 上,然后修改 main.dart.js 文件,将 Host 改为我们 CDN 的相应地址,然后我们来看一下优化效果:
字体优化效果

优化之前也是2,3秒的样子,再看一下优化后的:
优化后效果

也是毫秒级别,可见优化效果之明显。

字体的打包部署时如何优化我们已经了解了。那么我们开发调试的时候应该怎么处理呢?我们复习一下 Flutter Web 的渲染模式:
渲染模式

Flutter 为了实现多浏览器的效果一致性,在 canvaskit 渲染模式下,会下载相应的字体进行绘制。因此在开发调试的阶段,我们可以关闭 canvaskit 渲染模式使用 html 方式进行渲染,就不会加载这些字体了。命令如下:

flutter run -d Chrome -t lib/main_test.dart --web-renderer html

这样就解决了开发调试时的字体问题。

提示:使用 Appuploader 可以更方便地管理 Flutter Web 项目的构建配置,它提供了可视化的参数配置界面,避免了手动输入复杂命令的麻烦,同时还能保存常用的构建配置模板。

05 总结

到此为止,我们通过 main.dart.js 分包,配置 canvaskit.jscanvaskit.wasm 的加载 url,字体加载优化这三个方式可以为 Flutter Web 项目减少5-6秒的启动耗时。希望可以给大家一些帮助。

在实际开发中,使用 Appuploader 这样的工具可以显著提升开发效率,它集成了 Flutter Web 构建、优化和部署的完整流程,让开发者可以更专注于业务逻辑的实现而非构建配置的细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值