Flutter是用Skia构建的,这是一个用C++编写的2D图形库。Flutter的Skia引擎使其能够在各种平台上创建可移植的、性能良好的应用程序,包括网络。
大多数网络浏览器理解JavaScript语言;然而,Skia C++ API可以通过WebAssembly在网络浏览器上运行。WebAssembly通过将本地代码编译成浏览器能够理解的语言,使其能够在浏览器中运行。
在本教程中,我们将介绍WebAssembly并了解其优点。我们还将学习Flutter代码如何与WebAssembly绑定进行交互。
什么是WebAssembly?
WebAssembly(Wasm)是一种低级语言,以接近原生的性能运行。它是一种虚拟堆栈机,具有紧凑的二进制格式,旨在成为高级语言的编译目标。
WebAssembly使工程师能够用C、C++、Rust和其他高级语言编写代码,在浏览器中运行。Wasm把用高级语言编写的代码编译成WebAssembly模块。然后,它们被加载到一个网络应用程序中,并用JavaScript调用。
浏览器可以迅速将WebAssembly模块变成任何机器的指令:

我们为什么要使用WebAssembly?
JavaScript一直是浏览器理解的主要语言。然而,当运行资源密集型的应用程序,如3D游戏时,JavaScript往往会很迟钝。对于这样的应用程序,需要一个接近原生的体验。这就是Wasm出现的地方。
WebAssembly与JavaScript一起工作,为我们的应用程序提供一个接近原生的速度。由于模块的小尺寸,Wasm的加载和执行速度更快,在网络上实现高性能的应用程序。
Wasm使我们能够建立快速、高性能、可移植和内存安全的应用程序。它是一个开放的标准,旨在运行在其他平台上,而不仅仅是网络。许多流行的语言至少对WebAssembly有一些支持。
Dart和WebAssembly的互操作性
Dart网络平台使Dart代码可以在由JavaScript驱动的平台上编译和运行。我们也可以在我们的Dart代码中调用现有的JavaScript代码,这是由包提供的JavaScript绑定所实现的。 js包提供的JavaScript绑定。
从Dart代码中调用JavaScript代码和从JavaScript代码中调用Dart代码的能力被称为 "Dart-JavaScript互操作性":

js 包提供注解和函数,让我们指定我们的Dart代码如何与JavaScript代码连接。JavaScript API有WebAssembly 对象,这是所有与WebAssembly有关的函数的命名空间,它允许加载WebAssembly模块,创建新的内存和表实例,并处理WebAssembly错误。
WebAssembly有两种文件格式:
.wasm:包含二进制的汇编代码,是可执行文件。.wat: 包含人类可读的文本格式的.wasm文件,并编译为.wasm。它只用于编辑或调试。
编写WebAssembly代码可能是很痛苦的。大多数语言支持从我们的源代码中生成Wasm模块,然后我们可以使用提供的绑定来加载和调用。
我们可以通过Dart-web代码中的JavaScript WebAssembly对象,使用js 绑定,与WebAssembly一起工作。

为了在我们的Dart代码中利用js 绑定,用@JS 注释方法,并在其中添加external 关键字:
@JS('WebAssembly.instantiate')
external Object instantiate(Object bytesOrBuffer, Object import);
在Flutter网络应用中使用WebAssembly
我们可以使用各种语言来创建Wasm模块,并加载到我们的Flutter应用程序中。在这篇文章中,我们将使用AssemblyScript,一种类似TypeScript的WebAssembly语言,来生成Wasm模块。
使用AssemblyScript生成WebAssembly模块
为了开始,我们需要安装Node.js。你可以从Node的官方网站下载Node。
接下来,使用下面的命令安装npx ,一个npm包运行器:
npm i -g npx
创建一个新的目录和一个package.json 文件。然后使用下面的命令安装assemblyscript 和assemblyscript/loader:
mkdir wasm && cd wasm
npm init
npm i --save-dev assemblyscript
npm i --save @assemblyscript/loader
接下来,运行下面的命令来构建一个新的项目:
npx asinit .
该命令将生成assembly 和build 文件夹。我们将在index.ts 文件中编写我们的AssemblyScript模块,并将生成的Wasm代码放在build 文件夹中:

接下来,将下面的方法添加到index.ts 文件中。plusOne 函数在计数器上加一,而minusOne 函数从计数器上减一:
// The entry file of your WebAssembly module.
export function plusOne(n: i32): i32 {
return n+1;
}
export function minusOne(n:i32):i32{
return n - 1;
}
通过在根目录下运行npm run asbuild ,生成WebAssembly模块。这个命令会在build 文件夹中生成.wasm 和.wat 文件。我们将在我们的Flutter应用程序中利用release.wasm 文件:

在Flutter应用程序中使用WebAssembly模块
为了使用生成的Wasm模块,我们将把release.wasm 文件作为一个资产添加到我们的Flutter应用程序。我们还将使用wasm_interop包,它为我们处理JavaScript WebAssembly绑定,使我们能够通过调用暴露的方法与WebAssembly进行交互。
首先,使用flutter create . 命令在wasm 文件夹中创建一个新的Flutter应用程序。然后,创建一个新的assets/wasm 文件夹并添加生成的release.wasm 文件。更新pubspec.yaml 文件以包括assets文件夹和wasm_interop 包:
dependencies:
wasm_interop: ^2.0.1
flutter:
assets:
- assets/wasm/
运行flutter pub get ,添加依赖项。
更新main.dart 文件中的MyHomePage widget,如下所示:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
}
void _decrementCounter() {
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You current count is:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(
height: 100,
),
Wrap(
spacing: 100,
children: [
ElevatedButton(
onPressed: _incrementCounter,
child: const Text('Increment')),
ElevatedButton(
onPressed: _decrementCounter,
child: const Text('Decrement'))
],
)
],
),
),
);
}
}
在根目录下运行flutter run -d chrome ,在Chrome上提供应用程序。我们的应用程序有增量和减量按钮,将与我们的Wasm函数挂钩。

创建一个新的wasm_loader.dart 文件并添加WasmLoader 类。WasmLoader 类包含我们的Dart到Wasm的互操作逻辑:
import 'package:flutter/services.dart' show rootBundle;
import 'package:wasm_interop/wasm_interop.dart';
class WasmLoader {
WasmLoader({required this.path});
late Instance? _wasmInstance;
final String path;
Future<bool> initialized() async {
try {
final bytes = await rootBundle.load(path);
_wasmInstance = await Instance.fromBufferAsync(bytes.buffer);
return isLoaded;
} catch (exc) {
// ignore: avoid_print
print('Error on wasm init ${exc.toString()}');
}
return false;
}
bool get isLoaded => _wasmInstance != null;
Object callfunction(String name, int input) {
final func = _wasmInstance?.functions[name];
return func?.call(input);
}
}
上面的代码片段做了以下事情:
- 在类的构造函数中期待一个Wasm模块路径
- 在
initialized方法中加载Wasm模块文件 - 使用异步的
Instance.fromBufferAsync方法编译和实例化Wasm代码。这个方法利用了[WebAssembly.instantiate()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)JavaScript API - 如果Wasm代码被成功初始化,在
initialized方法中返回isLoaded状态 - 添加一个
callfunction方法,期望一个函数名称和参数,然后对该函数进行调用
最后,更新main.dart 文件中的MyHomePage widget,以利用WasmLoader:
late WasmLoader loader;
int _counter = 0;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
loader = WasmLoader(path: 'assets/wasm/release.wasm');
final isLoaded = await loader.initialized();
if (isLoaded) {
setState(() {});
}
}
void _incrementCounter() {
_counter = loader.callfunction('plusOne', _counter) as int;
setState(() {});
}
void _decrementCounter() {
_counter = loader.callfunction('minusOne', _counter) as int;
setState(() {});
}
上面的代码片断做了以下工作:
- 用我们的
.wasm文件的路径创建一个WasmLoader的实例 - 初始化
WasmLoader,并在初始化后更新应用程序的状态 - 用我们Wasm模块中调用
plusOne和minusOne函数的结果更新_counter属性
重新运行应用程序,点击增量和减量按钮--计数器相应地更新。你成功地在你的Flutter应用程序中使用了WebAssembly代码!
总结
在本教程中,我们讨论了WebAssembly,并研究了它在提高应用程序性能方面的一些好处。我们还研究了Flutter如何与JavaScript绑定进行交互。最后,我们使用AssemblyScript生成WebAssembly模块,并将其挂在我们的Flutter网络应用中。
有了这些,你现在可以在你的Flutter Web应用程序中使用WebAssembly,并提高其性能。本文中的所有代码都可以在GitHub上找到。
我希望你喜欢这个教程