热重载
使用 Flutter 的热重载功能加速开发。
Flutter 的热重载功能可帮助您快速轻松地进行实验、构建 UI、添加功能和修复错误。热重载的工作原理是将更新后的源代码文件注入到 Dart 虚拟机(Dart runtime)中。在 Dart 虚拟机使用新版本的字段和函数更新类之后,Flutter 框架会自动重建组件树,从而让您能够快速查看更改的效果。

DartPad 中的热重载演示
如何执行热重载
#要热重载 Flutter 应用
-
从受支持的 Flutter 编辑器或终端窗口运行应用。目标可以是物理设备或虚拟设备。只有处于调试模式(debug mode)的 Flutter 应用才能进行热重载或热重启。
-
修改项目中的其中一个 Dart 文件。大多数类型的代码更改都可以进行热重载;有关需要热重启的更改列表,请参阅特殊情况。
-
如果您使用的 IDE/编辑器支持 Flutter 的 IDE 工具并且启用了“保存时热重载”(hot reload on save),请选择 全部保存 (
cmd-s/ctrl-s),或点击工具栏上的热重载按钮。如果您正在使用命令行通过
flutter run运行应用,请在终端窗口中输入r。
热重载操作成功后,您会在控制台中看到类似于以下的信息
Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.
应用会更新以反映您的更改,并且应用的当前状态得以保留。您的应用会从执行热重载命令之前的位置继续运行。代码已更新,执行继续进行。

Android Studio 中运行、调试运行、热重载和热重启的控件
只有在更改后的 Dart 代码再次运行时,代码更改才会产生可见效果。具体来说,热重载会导致所有现有组件重建。只有参与组件重建的代码才会被自动重新执行。例如,main() 和 initState() 函数不会再次运行。
特殊情况
#接下来的章节将描述涉及热重载的具体场景。在某些情况下,对 Dart 代码进行少量更改即可让您继续使用热重载。在其他情况下,则需要进行热重启或完全重启。
应用被杀掉
#当应用被杀掉(例如应用在后台运行时间过长)时,热重载可能会失效。
编译错误
#当代码更改引入编译错误时,热重载会生成类似于以下内容的错误消息
Hot reload was rejected:
'/path/to/project/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here
Widget build(BuildContext context) {
^
'/path/to/project/lib/main.dart': error: line 33 pos 5: unbalanced ')'
);
^
在这种情况下,只需纠正指定 Dart 代码行上的错误即可继续使用热重载。
CupertinoTabView 的构建器
#热重载不会应用对 CupertinoTabView 的 builder 所做的更改。有关更多信息,请参阅 Issue 43574。
枚举类型
#当枚举类型更改为常规类,或者常规类更改为枚举类型时,热重载不起作用。
例如
更改前
enum Color { red, green, blue }
更改后
class Color {
Color(this.i, this.j);
final int i;
final int j;
}
泛型类型
#当修改泛型类型声明时,热重载不起作用。例如,以下操作将无法生效
更改前
class A<T> {
T? i;
}
更改后
class A<T, V> {
T? i;
V? v;
}
原生代码
#如果您更改了原生代码(例如 Kotlin、Java、Swift 或 Objective-C),则必须执行完全重启(停止并重启应用)才能使更改生效。
旧状态与新代码结合
#Flutter 的有状态热重载可以保留应用的状态。这种方法使您可以仅查看最近更改的效果,而无需丢弃当前状态。例如,如果您的应用要求用户登录,您可以修改并热重载导航层级中深层的页面,而无需重新输入登录凭据。状态得以保留,这通常是我们想要的行为。
如果代码更改影响了应用的状态(或其依赖项),则应用所处理的数据可能与从头开始执行时的数据不完全一致。热重载之后的结果可能会与热重启之后的结果不同。
包含了最近的代码更改,但未包含应用状态
#在 Dart 中,静态字段是延迟初始化的。这意味着第一次运行 Flutter 应用并读取静态字段时,它会被设置为初始化器评估后的值。全局变量和静态字段被视为状态,因此在热重载期间不会重新初始化。
如果您更改了全局变量和静态字段的初始化器,则需要进行热重启或完全重启以查看更改。例如,考虑以下代码
final sampleTable = [
Table(
children: const [
TableRow(children: [Text('T1')]),
],
),
Table(
children: const [
TableRow(children: [Text('T2')]),
],
),
Table(
children: const [
TableRow(children: [Text('T3')]),
],
),
Table(
children: const [
TableRow(children: [Text('T4')]),
],
),
];
运行应用后,您进行了以下更改
final sampleTable = [
Table(
children: const [
TableRow(children: [Text('T1')]),
],
),
Table(
children: const [
TableRow(children: [Text('T2')]),
],
),
Table(
children: const [
TableRow(children: [Text('T3')]),
],
),
Table(
children: const [
TableRow(
children: [Text('T10')], // modified
),
],
),
];
您执行了热重载,但更改未生效。
相反,在以下示例中
const foo = 1;
final bar = foo;
void onClick() {
print(foo);
print(bar);
}
首次运行应用时打印 1 和 1。然后,您进行了以下更改
const foo = 2; // modified
final bar = foo;
void onClick() {
print(foo);
print(bar);
}
虽然 const 字段值的更改总是会热重载,但静态字段的初始化器不会重新运行。从概念上讲,const 字段被视为别名而不是状态。
Dart 虚拟机能够检测到初始化器的更改,并在需要热重启才能生效时进行标记。上述示例中的大多数初始化工作都会触发该标记机制,但以下情况除外
final bar = foo;
要更新 foo 并在热重载后查看更改,请考虑将该字段重新定义为 const,或者使用 getter 返回值,而不是使用 final。例如,以下任一解决方案都有效
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
print(foo);
print(bar);
}
const foo = 1;
int get bar => foo; // ...or provide a getter.
void onClick() {
print(foo);
print(bar);
}
有关更多信息,请阅读关于 Dart 中 const 和 final 关键字的区别。
排除了最近的 UI 更改
#即使热重载操作看起来成功且没有生成异常,某些代码更改也可能在刷新后的 UI 中不可见。这种行为在更改应用的 main() 或 initState() 方法后很常见。
通常情况下,如果修改后的代码位于根组件的 build() 方法之下,则热重载会按预期工作。但是,如果修改后的代码不会因为组件树的重建而重新执行,那么您在热重载后将看不到其效果。
例如,考虑以下代码
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(onTap: () => print('tapped'));
}
}
运行此应用后,将代码更改为如下所示
import 'package:flutter/widgets.dart';
void main() {
runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr)));
}
通过热重启,程序从头开始,执行新版本的 main(),并构建一个显示文本 Hello 的组件树。
但是,如果您在此更改后热重载应用,则不会重新执行 main() 和 initState(),组件树将使用未更改的 MyApp 实例作为根组件进行重建。这导致热重载后没有可见的变化。
工作原理
#当调用热重载时,宿主机(host machine)会查看自上次编译以来编辑的代码。以下库将被重新编译
- 任何包含已更改代码的库
- 应用的主库
- 从主库到受影响库路径上的所有库
这些库中的源代码被编译成 kernel 文件并发送到移动设备的 Dart 虚拟机。
Dart 虚拟机从新的 kernel 文件中重新加载所有库。到目前为止,还没有代码被重新执行。
热重载机制随后触发 Flutter 框架对所有现有组件和渲染对象进行重建/重新布局/重绘。