Dart简介
目标是高效地开发多平台应用,提供灵活的运行时环境和编译工具。
理论上所有高级语言都可跨平台,关键在于语法、平台库好不好用,编译工具成不成熟。下面会详细介绍下Dart的编译和执行
Dart SDK安装
参考Dart SDK安装。Dart SDK中包含了Dart核心库、编译器、命令行工具等。Dart SDK是由Dart源码编译出来的归档文件。
- Flutter SDK内置了Dart SDK工具(Dart SDK归档文件,包括工具和核心库),位于
{flutter_sdk}/bin/cache/dart-sdk中,不需要再单独下载。 - Flutter Engine中依赖了Dart的源码,位于
{flutter_engine}/third_party/dart中,通过ninja构建出Dart SDK可执行程序,供Flutter SDK使用。
Dart语言
支持众多特性:类型安全(静态类型检查、dynamic运行时检查)、空安全、异步调用、流、箭头函数、getter函数等。基本语法参考Dart语言
Dart库
Dart项目文件
Dart使用pubspec.yaml文件保存项目信息、发布信息、依赖包等。pubspec说明
类似npm的
package.json,本地会缓存依赖包,不同项目可以共用本地缓存的依赖包
可以使用dart pub <subcommand>命令管理项目,如add添加依赖,get获取依赖等。可以用dart pub -h查看帮助,也可以看dart pub说明
如果用dart开发Flutter程序,使用
flutter pub <subcommand>命令替代,flutter对dart命令进行了一层包装
新建pubspec.yaml文件
1 | name: myapp # 项目名称 |
执行dart pub get获取依赖,会生成几个文件。不需要提交,加到.gitignore中
pubspec.lock:保存项目信息.packages:已经弃用,替换为package_config.json文件.dart_tool/package_config.json:将依赖包映射到系统缓存该包的路径
main.dart中可以导入包使用,运行时会从package_config.json中查找依赖包路径
1 | import 'package:js/js.dart' as js; |
dart compile和dart --snapshot可以使用--packages=<path>选项指定.packages文件或者package_config.json文件,用于编译时查找依赖包路径
Dart工具
Dart SDK中提供了一些工具,使用-h查看帮助或者参考Dart命令行工具。源码入口位于{dart_sdk}/pkg/dartdev/中

dart:用于创建、格式化、分析、测试、编译和运行dart代码dartaotruntime:用于执行aot预编译过机器码dartdoc:用于生成API文档
除了上面三个工具外,还有dart2js、dart2native、dartanalyzer、dartdevc、dartfmt、pub等工具。这些工具从2.10版本开始全部被封装到了dart中,通过dart <subcommand>的方式执行:
dart2native,dart2aot,dart2js工具被dart compile替代dartanalyzer被dart analyze替代dartfmt被dart format替代pub被dart pub替代
{flutter_sdk}/bin/dart对dart-sdk的工具做了一层包装,执行的时候会调用dart-sdk中的工具。
由于配置Flutter SDK环境变量
{flutter_sdk}/bin的时候没有配置dart-sdk的环境变量,如果要使用dartaotruntime工具,需要进入对应目录执行,或者给dart-sdk也配置环境变量
Dart的编译和执行
Dart虚拟机
Dart虚拟机源码位于{dart_sdk}/runtime/vm中,包含以下几个部分:

DartVM作为虚拟机为Dart高级语言提供执行环境,但这并不表示Dart一定运行在虚拟机中。Dart的运行主要有几种方式:
- 虚拟机执行:通过JIT即时编译+解释器,执行Dart源文件或者Kernel二进制文件,运行在Dart虚拟机中。对应
dart run命令 - 目标代码执行:通过AOT预编译成目标代码,运行在预编译运行时环境(Precompiled Runtime)中。不包含编译器,因此无法动态加载Dart源码。对应
dartaotruntime命令
- 开发阶段:运行在Dart虚拟机中,通过Dart虚拟机提供的即时编译器(JIT)执行,支持增量编译,热重载和调试。
- 发布阶段:通过Dart的AOT编译器编译成目标平台的代码,在Dart预编译运行时(Precompiled Runtime)中执行,提高启动速度和执行效率。
Dart 2之后,Dart VM不支持直接执行源代码,只接收Kernel AST序列化成的Kernel二进制文件(即.dill文件)。通过Dart的编译前端(CFE,common front-end)编译,并被其他工具所依赖使用,例如Dart VM、dart2js、Dart Dev Compiler。
Dart运行时会被打包到Self-Contained的目标可执行程序中,同时也是Dart虚拟机的一部分,包含以下功能
- 内存管理:提供对象分配和分代垃圾回收功能。
- 运行时类型检查和强制转换
- 管理
isolates:包括主isolate和应用自行创建的isolate
虚拟机执行
使用dart run命令启动虚拟机执行程序,如下
新建
main.dart文件1
2
3
4//main.dart
void main() {
print('Hello, World!');
}执行
dart main.dart,输出”Hello, World!”
run子命令启动一个Dart虚拟机,执行未编译过的源码或者部分快照类型(JIT、Kernel快照),不支持执行aot快照。
可以省略,例如
dart main.dart,dart main.dill
Dart编译
compile命令
dart compile命令封装了不同场景下的编译,不需要手动执行编译前端和编译后端。源码位于{dart_sdk}/pkg/dartdev/lib/src/commands/compile.dart,分为以下几种方式:
exe:生成Self-Contained可执行文件,包含生成的目标代码和一个小型的Dart运行时,可以直接运行- 编译:
dart compile exe main.dart,生成main.exe文件 - 运行:
./main.exe,输出”Hello, World!”
- 编译:
aot-snapshot:生成AOT快照文件,包含生成的目标代码,但不包含Dart运行时,需要使用dartaotruntime执行- 编译:
dart compile aot-snapshot main.dart,生成main.aot文件 - 执行
dartaotruntime main.aot,输出”Hello, World!”
- 编译:
jit-snapshot:生成JIT快照文件,包含生成的目标代码,不同的是在训练运行期间已经加载和解析过代码,使用dart run运行。由于在训练运行期间已经解析和编译过,Dart虚拟机不需要再进行解析和编译,因此可以更快的执行代码。(经过训练和优化,有可能比aot执行更快)- 编译:
dart compile jit-snapshot main.dart,生成main.jit文件,并且会执行一遍程序训练,输出”Hello, World!” - 运行:
dart run main.jit,输出”Hello, World!”
- 编译:
kernel:生成.dill二进制的kernel快照文件,是一种中间代码,和平台无关,具有可移植性。包含二进制格式的Dart抽象语法树(Kernel AST)- 编译:
dart compile kernel main.dart,生成main.dill文件 - 执行:
dart run main.dill,输出”Hello, World!”
- 编译:
js:生成js文件- 编译:
dart compile js main.dart,生成out.js文件 - 可以使用
webdev serve命令启动开发服务器运行js
- 编译:
exe和aot-snapshot存在一些限制:
- 不支持交叉编译、只能本地编译本地运行:需要在macOS、Windows、Linux主机上分别编译出三个目标程序
- 生成的可执行程序不支持签名
- 不支持
dart:mirrors(用于动态反射)和dart:developer(用于调试检查)库,参考Dart核心库说明
对比下编译产物文件,如下:exe > jit-snapshot > aot-snapshot > kernel > dart source code,一般情况下执行效率刚好相反。

查看dart compile源码,如下:
1 | //{dart_sdk}/pkg/dartdev/lib/src/commands/compile.dart |
dart –snapshot
在Flutter SDK中经常看到.snapshot后缀的文件,如flutter_tools.snapshot,查看Flutter命令脚本的源码中使用了dart --snapshot命令,官网没有说明。使用dart --snapshot查看帮助如下:

--snapshot用于生成快照文件,--snapshot-kind指定生成JIT快照还是kernel快照。默认生成kernel快照。
dart --snapshot源码入口位于{dart-sdk}/runtime/bin/main.cc
CompileSnapshotCommand
查看CompileSnapshotCommand代码,对应dart compile kernel和dart compile jit-snapshot命令。实际就是调用dart --snapshot-kind=$formatName执行。如下:
1 | //{dart_sdk}/pkg/dartdev/lib/src/commands/compile.dart |
dart compile kernel/jit-snapshot等价于dart --snapshot-kind=kernel/app-jits。只不过是新版本Dart工具统一封装到compile中而已。
例如
dart --snapshot=main.snapshot main.dart生成main.snapshot,dart compile kernel main.dart生成main.dill。main.dill和main.snapshot实际上是一样的,都是Kernel快照文件,文件大小也相同。
CompileNativeCommand
查看CompileNativeCommand代码,对应dart compile aot和dart compile exe命令,调用了dart2native的generateNative方法,
1 | //{dart_sdk}/pkg/dartdev/lib/src/commands/compile.dart |
generateNative流程如下:
1 | //{dart-sdk}/pkg/dart2native/lib/generate.dart |
编译流程
Dart编译前端(frontend,
{dart_sdk}/pkg/front_end/lib/src/api_prototype/front_end.dart):将Dart源码编译为Kernel二进制文件,是一种平台无关的中间代码。Dart编译前端生成的.dill文件类似于Java编译前端的.class文件,通过虚拟机执行,和平台无关。
Dart编译后端(gen_snapshot,
{dart_sdk}/runtime/bin/gen_snapshot.cc):将Kernel二进制文件编译出目标代码- 将Kernel二进制代码生成一个控制流图(CFG,control flow graph),CFG由中间语言(IL,Intermediate Language)指令组成。
- 对IL指令进行优化
- CFG编译成机器码,每个IL指令对应多个机器指令
IL指令类似于虚拟机指令,从堆栈中获取操作数,执行操作,将结果推送到堆栈中
创建exe可执行文件:
writeAppendedExecutable方法合并dartaotruntime和aot目标代码
命令如下:
1 | 编译前端1 |
gen_kernel.dart.snapshot和frontend_server.dart.snapshot都是调用front_end的方法(可以分析源码,或者执行命令异常查看方法调用栈),参数稍微有些差异
例如
frontend_server.dart.snapshot支持--import-dill参数,可以加载和链接其他dill文件。
gen_kernel.dart.snapshot的--platform参数用于指定sdk完整路径,用于将平台库加载到产物中。与frontend_server.dart.snapshot的--sdk-root和--platform参数作用相同,--sdk-root指定文件夹路径,--platform指定文件名,加起来是完整路径。
- Dart平台库位于
{dart-sdk}/lib/_internal/vm_platform_strong.dill。 - Flutter平台库中包含Dart库和Flutter框架本身,位于
{flutter_sdk}/bin/cache/artifacts/engine/common/flutter_patched_sdk/platform_strong.dill
--target参数:使用Dart平台库时,target值需要为vm,使用Flutter平台库时,target值需要为flutter,否则会编译失败。说明如下:
1 | ./dart-sdk/bin/dart ./dart-sdk/bin/snapshots/frontend_server.dart.snapshot -h |
target相关源码位于
{dart-sdk}/pkg/vm/lib/target/下
注:target为flutter时,无法使用dartaotruntime执行aot文件,由于找不到对应的平台库,会报错Dart_LookupLibrary: library 'dart:_builtin' not found.。
Kernel文件踩坑
dart compile kernel生成的Kernel文件不能用于gen_snapshot后端编译,会报错:Unable to use class Library:'dart:core' Class: _List@0150898 which is not loaded yet.。但是可以使用dart run main.dill运行。- 编译前端
--no-aot参数生成的Kernel文件不能用于gen_snapshot后端编译,会报错:error: Missing table selector metadata! Probably gen_kernel was run in non-AOT mode or without TFA.。但是可以使用dart run main.dill运行 - 编译前端
--aot参数生成的Kernel文件可以用于gen_snapshot后端编译,但是不能直接用dart运行,会报错:error: vm.procedure-attributes.metadata metadata is allowed in precompiled mode only
上述三者都生成了Kernel文件,但是不太一样。具体区别暂时不清楚。
Dart SDK版本一致
Dart编译前端、编译后端、以及Dart运行时的版本必须一致,否则会报错版本不匹配。例如
1 | Dart运行不同版本的编译前端报错 |
生成的dill文件和aot文件中带了版本信息,执行的时候会进行校验。
主机上有多个版本Dart SDK,例如Flutter引擎编译出来的Dart SDK和Flutter SDK中内置的Dart SDK版本不一致。此时需要分别进入对应路径下执行命令,或者配置多个环境变量。为了避免麻烦,可以切换Flutter引擎到对应的commit id,保持版本一致。
Web平台
dart支持在Web平台上执行,既不是JIT也不是AOT:生成JavaScript代码,运行在浏览器中,而不是目标平台代码
- 开发阶段使用
dartdevc增量式编译器 - 生产环境使用
dart2js编译器,高版本替换为dart compile js命令
官方建议使用webdev工具,而不是直接使用dartdevc和dart2js工具。
webdev serve:编译并部署到开发服务器,使用localhost:8080访问。默认使用dartdevc编译。添加--release选项,替换为dart2js编译webdev build:默认使用dart2js编译,添加--no-release选项,替换为dartdevc编译
Dart源码下载和编译
Dart SDK是Dart源码的编译产物。
源码下载和编译
安装
depot_tools- 下载:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git - 设置环境变量:
.bash_profile文件中添加export PATH=/your_path/depot_tools/:$PATH
- 下载:
下载Dart源码和DEPS依赖
1
2
3
4
5创建目录
mkdir dart-sdk
cd dart-sdk
通过gclient下载dart源码和DEPS依赖
fetch dart编译Dart SDK源码,生成Dart SDK归档文件
1
2
3
4cd sdk
./tools/build.py --no-goma --mode release --arch x64 create_sdk
也可以指定多个build_targets编译
./tools/build.py --mode=release --arch=x64 create_sdk runtime gen_snapshot frontend_server.dart.snapshot
build_targets参数没有找到具体说明,不过可以参考tools/bots/test_matrix.json文件中的builder_configurations踩坑:FlutterEngine中下载的Dart SDK(
{flutter_engine}/src/third_party/dart)不包含编译需要的依赖项目,无法直接用于编译,因此需要使用gclient重新下载Dart源码。应该也可以手动创建
.gclient文件,执行gclient sync下载依赖
交叉编译
编译生成Dart虚拟机
交叉编译Android平台的Dart VM,可以在Android平台执行Dart应用程序
- 下载Android需要的依赖,如NDK,SDK等
- 修改dart-sdk目录下的
.gclient文件:最后一行添加target_os = ['android'] - 下载依赖项目:执行
gclient sync
- 修改dart-sdk目录下的
- 交叉编译Arm64的Android平台的Dart VM:
./tools/build.py --no-goma --arch=arm64 --mode=release --os=android runtime - 使用adb将编译后的Dart VM工具push到Android平台中:
adb push ./xcodebuild/ReleaseAndroidARM64/dart /data/local/tmp/dart - 将Dart程序push到Android平台中:
adb push main.dart /data/local/tmp/ - 运行Dart程序:
adb shell /data/local/tmp/dart /data/local/tmp/main.dart
踩坑:这里无法使用adb shell进入Android终端执行,会报错,缺少工具和库。
编译生成Dart SDK
可以将编译出来的整个SDK归档文件push到Android平台中并设置环境变量,此时可以像在主机上一样使用Dart SDK,编译和执行Dart应用。如下
1 | 交叉编译Android平台的Dart SDK |
结语
了解Dart编译方式和不同产物,以及执行原理,主要有以下场景
- 为动态化,分包等提供一些思路
- 使用交叉编译在嵌入式平台中执行Dart或Flutter程序
- 定制虚拟机、编译器等。
参考资料: