基于 Flutter SDK & Dart VM 源码
Hot Reload 是 Flutter 提供的亚秒级代码热更新能力,无需重启应用即可看到代码更改效果
| 特性 | 时间 | 原理 | 状态保留 |
|---|---|---|---|
| Hot Reload | <1s | 增量编译 + Widget 重建 | ✅ |
| Hot Restart | 2-5s | 完整重启应用状态 | ❌ |
| Full Restart | 10-30s | 重新编译 + 部署 | ❌ |
开发效率提升 10x+
┌─────────────────────────────────────────────────────┐
│ 开发者机器 │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Dart Code │───▶│ flutter_tools │ │
│ │ (.dart) │ │ (HotRunner) │ │
│ └──────────────┘ └───────────┬──────────────┘ │
│ │ │
│ ┌───────────▼──────────────┐ │
│ │ frontend_server │ │
│ │ (Kernel 编译器) │ │
│ └───────────┬──────────────┘ │
│ │ .dill │
└──────────────────────────────────┼──────────────────┘
│
───────────────┼───────────────
│
┌──────────────────────────────────▼──────────────────┐
│ 目标设备 │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ DevFS │◀───│ Dart VM │ │
│ │ (虚拟文件系统) │ │ (Isolate) │ │
│ └──────────────┘ └───────────┬──────────────┘ │
│ │ reassemble │
│ ┌───────────▼──────────────┐ │
│ │ Flutter Framework │ │
│ │ (Widget Tree) │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────┘
| 组件 | 职责 | 源码位置 |
|---|---|---|
| HotRunner | 协调整个 Hot Reload 流程 | run_hot.dart |
| frontend_server | 增量编译 Dart 代码 | frontend_server |
| DevFS | 设备端虚拟文件系统 | devfs.dart |
| VM Service | 与 Dart VM 通信 | vm_service |
| WidgetBinding | 触发 Widget 重建 | binding.dart |
源码:packages/flutter_tools/lib/src/run_hot.dart
class HotRunner extends ResidentRunner {
final List<FlutterDevice> devices;
final DebuggingOptions debuggingOptions;
// 核心方法
Future<int> restart({
bool fullRestart = false,
bool pause = false,
String? reason,
}) async { ... }
Future<OperationResult> _reloadSources({
required String? reason,
}) async { ... }
}
// 1. 启动应用
Future<int> attach() async {
await _setupTerminal();
await _connectToDevices();
return _attach();
}
// 2. 监听文件变化
void _setupTerminal() {
terminal.setupTerminal();
terminal.singleCharMode = true;
// 监听 'r' 键触发 Hot Reload
// 监听 'R' 键触发 Hot Restart
}
// 3. 建立设备连接
Future<void> _connectToDevices() async {
for (final device in devices) {
await device.connect();
}
}
Flutter Tools 通过 ProjectFileInvalidator 监听文件变化
class ProjectFileInvalidator {
Future<InvalidatedSources> checkForChanges({
required DateTime lastModified,
required Uri mainFile,
required Set<Uri> dependencies,
}) async {
// 检查依赖文件是否被修改
final modifiedFiles = <Uri>[];
for (final file in dependencies) {
final stat = await fileSystem.stat(file);
if (stat.modified.isAfter(lastModified)) {
modifiedFiles.add(file);
}
}
return InvalidatedSources(modifiedFiles);
}
}
// 使用示例
final result = await projectFileInvalidator.checkForChanges(
lastModified: lastCompileTime,
mainFile: mainFile.uri,
dependencies: projectDependencies,
);
if (result.modified.isNotEmpty) {
// 触发 Hot Reload
await _reloadSources();
}
Flutter 使用 frontend_server 实现增量编译,基于 Kernel 二进制格式
源码:pkg/frontend_server/
class FrontendServer {
// 增量编译核心方法
Future<CompilerOutput> recompile({
required Uri entryPoint,
required List<Uri> invalidatedFiles,
required String outputPath,
}) async {
// 1. 解析修改的文件
// 2. 增量更新 Kernel AST
// 3. 生成增量 .dill 文件
return CompilerOutput(outputPath);
}
}
┌──────────────┐
│ Dart Source │ (.dart 文件)
└──────┬───────┘
│ 解析 (Parser)
┌──────▼───────┐
│ AST 节点 │ (抽象语法树)
└──────┬───────┘
│ 类型推断
┌──────▼───────┐
│ Kernel AST │ (类型化 AST)
└──────┬───────┘
│ 序列化
┌──────▼───────┐
│ .dill 文件 │ (Kernel Binary)
└──────────────┘
Kernel 是 Dart 的中间表示格式,包含完整的类型信息
// HotRunner 中的增量编译调用
Future<CompilerOutput> _recompile() async {
final compiler = device.generator;
final output = await compiler.recompile(
mainFile.uri,
invalidatedFiles, // 修改的文件列表
outputPath: dillPath,
packagesFilePath: packagesFilePath,
);
return output;
}
增量编译会复用之前的编译结果,只重新编译受影响的部分
.dill 是 Kernel Binary 格式,包含编译后的 Dart 代码
| 内容 | 说明 |
|---|---|
| Library 定义 | 所有库的元数据 |
| Class 定义 | 类的结构信息 |
| Procedure 定义 | 方法签名和实现 |
| 常量池 | 编译时常量 |
| 类型表 | 所有类型定义 |
Hot Reload 传输的是增量 .dill,只包含修改的部分
DevFS (Device File System) 是运行在目标设备上的虚拟文件系统,用于存储 Flutter 资源和编译产物
┌─────────────────────────────────────────────┐
│ 开发机器 │
│ ┌────────────────────────────────────┐ │
│ │ flutter_tools │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ DevFS Writer │ │ │
│ │ └───────────┬────────────────┘ │ │
│ └──────────────┼─────────────────────┘ │
│ │ VM Service Protocol │
└─────────────────┼──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 目标设备 │
│ ┌────────────────────────────────────┐ │
│ │ Dart VM │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ DevFS (虚拟文件系统) │ │ │
│ │ │ - /main.dart.dill │ │ │
│ │ │ - /incremental.dill │ │ │
│ │ │ - /assets/* │ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
// DevFS 同步实现
Future<UpdateFSReport> _updateDevFS() async {
// 1. 创建 DevFS (如果不存在)
if (!device.devFS!.isCreated) {
await device.devFS!.create();
}
// 2. 同步文件到设备
final report = await device.devFS!.update(
fileSystem: fileSystem,
files: modifiedFiles,
bundle: assetBundle,
);
return report;
}
class DevFS {
// 通过 VM Service 写入文件
Future<void> writeStringFile(
String path,
String content,
) async {
final base64Content = base64Encode(
utf8.encode(content)
);
await vmService.writeDevFSFile(
fsName: name,
path: path,
file: base64Content,
);
}
}
文件通过 Base64 编码后通过 VM Service Protocol 传输
Dart VM 提供的调试和监控协议
| RPC 方法 | 用途 |
|---|---|
| reloadSources | 重载源代码 |
| getVM | 获取 VM 信息 |
| getIsolate | 获取 Isolate 信息 |
| evaluate | 执行表达式 |
| setLibraryDebuggable | 设置调试状态 |
// 连接到 VM Service
final vmService = await vmServiceConnectUri(
'ws://localhost:12345/ws',
);
// 获取 VM 信息
final vm = await vmService.getVM();
// 获取主 Isolate
final isolate = vm.isolates!.first;
final isolateId = isolate.id!;
VM Service 通过 WebSocket 连接,支持双向通信
核心 RPC:reloadSources
{
"jsonrpc": "2.0",
"method": "reloadSources",
"params": {
"isolateId": "isolates/12345678",
"force": false
},
"id": "req-1"
}
VM 会加载 DevFS 中的新 .dill 文件,并更新运行时类型
// flutter_tools 中的调用
Future<ReloadReport> _reloadSourcesInVM() async {
final report = await device.vmService!.reloadSources(
isolateId,
force: false,
pause: false,
);
if (report.success!) {
// 重载成功
return report;
} else {
// 重载失败,需要 Hot Restart
throw HotReloadFailedException();
}
}
reloadSources RPC
│
▼
┌──────────────────┐
│ Read DevFS │ 读取增量 .dill
└────────┬─────────┘
│
▼
┌──────────────────┐
│ KernelLoader │ 解析 Kernel
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Update Types │ 更新类型定义
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Morph Instance │ 实例变形
└──────────────────┘
当类定义发生变化时,VM 需要更新现有实例的类型
| 修改类型 | 处理方式 |
|---|---|
| 添加字段 | 新字段初始化为 null |
| 删除字段 | 字段被忽略 |
| 修改方法 | 方法被替换 |
| 修改构造函数 | 需要 Hot Restart |
源码:runtime/vm/kernel_loader.cc
// 实例变形核心逻辑
class InstanceMorpher {
// 将旧类实例变形为新类实例
void MorphInstances(
const Class& old_class,
const Class& new_class,
) {
// 1. 遍历所有旧类实例
// 2. 调整内存布局
// 3. 迁移字段值
// 4. 更新类型指针
}
}
VM 重载完成后,需要触发Widget 树重建
// 触发 reassemble
Future<void> _reassemble() async {
await device.vmService!.evaluate(
isolateId,
'WidgetsBinding.instance.reassemble()',
);
}
reassemble 会触发所有 Widget 的重建,但保留 State 状态
源码:packages/flutter/lib/src/widgets/binding.dart
class WidgetsBinding {
Future<void> performReassemble() async {
// 1. 标记所有 Element 为 dirty
try {
await reassemble();
} finally {
// 2. 触发帧调度
scheduleWarmUpFrame();
}
}
Future<void> reassemble() async {
await rootElement!.reassemble();
}
}
reassemble()
│
▼
Element.reassemble()
│
├─▶ State.reassemble()
│ └─▶ 调用 setState(() {})
│
└─▶ Element.performRebuild()
│
├─▶ Widget.build()
│
└─▶ Element.update()
│
└─▶ 更新 RenderObject
重建 Widget 树的同时保留应用状态
class _MyWidgetState extends State<MyWidget> {
int _counter = 0; // ✅ 保留
void reassemble() {
super.reassemble();
// Hot Reload 时调用
// 可在此处清理缓存
}
void initState() {
super.initState();
// 仅首次创建时调用
// Hot Reload 不会调用
}
}
Hot Restart 是完全重启应用,但不需要重新编译
适用场景:Hot Reload 无法处理的修改
// Hot Restart 实现
Future<int> restart({bool fullRestart = true}) async {
if (fullRestart) {
// 1. 停止当前应用
await stopApp();
// 2. 完整编译
final dill = await _compile(
fullRestart: true,
);
// 3. 重新启动
await startApp(dill);
}
}
| 特性 | Hot Reload | Hot Restart |
|---|---|---|
| 速度 | <1s | 2-5s |
| 状态保留 | ✅ 是 | ❌ 否 |
| main() 修改 | ❌ 不支持 | ✅ 支持 |
| 全局变量 | ❌ 不重置 | ✅ 重置 |
| 编译方式 | 增量编译 | 完整编译 |
| dill 文件 | 增量 .dill | main.dart.dill |
// ❌ 不支持 Hot Reload
enum Status {
loading,
success,
error,
cancelled, // 添加新值
}
// 原因:枚举值在编译时确定索引
// 运行时无法动态添加
解决方案:使用 Hot Restart 或将枚举改为类
// ❌ 不支持 Hot Reload
class MyClass<T extends num> { // 修改泛型约束
final T value;
MyClass(this.value);
}
// 原因:泛型实例化在运行时已确定
// 修改约束会导致类型不兼容
// ❌ 不支持 Hot Reload
int globalCounter = 100; // 修改初始值
void main() {
runApp(MyApp());
}
// 原因:全局变量初始化只在启动时执行一次
// Hot Reload 不会重新执行初始化
解决方案:将全局变量改为 State 中的变量
| 命令 | 作用 |
|---|---|
| r | 触发 Hot Reload |
| R | 触发 Hot Restart |
| v | 打开 DevTools |
| w | 显示 Widget 树 |
| t | 显示渲染树 |
| q | 退出应用 |
flutter/packages/flutter_tools/ ├── lib/ │ └── src/ │ ├── run_hot.dart # HotRunner │ ├── resident_runner.dart # 基类 │ ├── devfs.dart # DevFS 管理 │ └── compile.dart # 编译逻辑 │ dart-sdk/runtime/vm/ ├── kernel_loader.cc # Kernel 加载 ├── object.cc # 对象模型 └── isolate.cc # Isolate 管理
| 文件 | 说明 |
|---|---|
| run_hot.dart | Hot Runner 主逻辑 |
| frontend_server.dart | 增量编译服务器 |
| devfs.dart | 设备文件系统 |
| vm_service.dart | VM Service 客户端 |
| binding.dart | Flutter 绑定层 |
| kernel_loader.cc | Kernel 加载器 |
// 自定义 Hot Reload 行为
class MyCustomWidget extends StatefulWidget {
@override
State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget> {
@override
void reassemble() {
super.reassemble();
// Hot Reload 时执行
// 清理缓存、重置状态等
}
}
// 启用详细日志
flutter run -v
// 查看 VM Service 日志
flutter run --disable-service-auth-codes
// 连接 DevTools
flutter pub global activate devtools
flutter pub global run devtools
文件变化 → 增量编译 → DevFS 同步 → VM Reload → reassemble → Widget 重建
| 组件 | 职责 |
|---|---|
| HotRunner | 协调器 |
| frontend_server | 增量编译 |
| DevFS | 文件传输 |
| Dart VM | 代码重载 |
| WidgetBinding | UI 重建 |
1. 文件监听 └─▶ ProjectFileInvalidator 检测变化 2. 增量编译 └─▶ frontend_server 生成增量 .dill 3. DevFS 同步 └─▶ 通过 VM Service 传输文件 4. VM 重载 └─▶ reloadSources RPC 加载新代码 5. 类型更新 └─▶ InstanceMorpher 更新实例 6. Widget 重建 └─▶ reassemble 触发重建
⚡ Happy Fluttering!