热重载
Flutter 的热重载功能帮助你快速轻松地进行实验、构建 UI、添加功能和修复错误。热重载通过将更新的源代码文件注入正在运行的Dart 虚拟机 (VM) 来工作。在 VM 使用字段和函数的新版本更新类之后,Flutter 框架会自动重建小部件树,允许你快速查看更改的效果。
如何执行热重载
#要热重载 Flutter 应用:
从支持的Flutter 编辑器 或终端窗口运行应用。物理设备或虚拟设备都可以作为目标。只有处于调试模式的 Flutter 应用才能进行热重载或热重启。
修改项目中的一个 Dart 文件。大多数类型的代码更改都可以热重载;有关需要热重启的更改列表,请参阅特殊情况。
如果你正在使用支持 Flutter IDE 工具的 IDE/编辑器,请选择 全部保存 (
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
所做的更改。有关更多信息,请参阅问题 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 VM 会检测初始化程序更改,并在更改集需要热重启才能生效时发出标志。对于上述示例中的大多数初始化工作,都会触发标记机制,但对于以下情况则不会:
final bar = foo;
要更新 foo
并查看热重载后的更改,请考虑将字段重新定义为 const
或使用 getter 返回值,而不是使用 final
。例如,以下任一解决方案都有效:
const foo = 1;
const bar = foo; // 将 foo 转换为 const...
void onClick() {
print(foo);
print(bar);
}
const foo = 1;
int get bar => foo; // ...或提供一个 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
实例作为根小部件重建小部件树。这导致热重载后没有可见的更改。
工作原理
#调用热重载时,主机查找自上次编译以来已编辑的代码。将重新编译以下库:
- 任何带有更改代码的库
- 应用程序的主库
- 来自主库到受影响库的库
这些库的源代码编译成内核文件 并发送到移动设备的 Dart VM。
Dart VM 从新的内核文件中重新加载所有库。到目前为止,没有重新执行任何代码。
然后,热重载机制会导致 Flutter 框架触发所有现有小部件和渲染对象的重建/重新布局/重新绘制。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。