flutter 通过 Hot Reload 可以实现代码的动态刷新,可以帮助开发者方便快速的调试代码,构建 UI,主要是调试UI。
使用
Android Studio上直接cmd+s保存代码就会启动Hot Reload,需要注意的是只有 Debug 模式才能使用 Hot Reload。
工作原理
Hot Reload 只能在 Debug 模式下使用,是因为 Debug 模式下,Flutter 采用的是 JIT 动态编译,代码是运行在 Dart VM 上,JIT 将 Dart 编译成可以运行在 Dart VM 上的 Dart Kernel,Dart Kernel 可以动态更新,所以就实现了代码的实时更新功能。
当调用 Hot Reload 时:
- 首先会扫描代码,找到上次编译之后有变化的 Dart 代码。
- 在将这些变化的 Dart 代码转化为增量的 Dart Kernel 文件。
- 将增量的 Dart Kernel 文件发送到正在移动设备上运行的 Dart VM。
- Dart VM 会将发来的增量 Dart Kernel 文件和原有的 Dart Kernel 文件合并,然后重新加载全新的 Dart Kernel。
- 重新加载了 Dart Kernel,却不会重新执行代码,而是通知 Flutter Framework 重建 Widget。
所以 Flutter 的 Hot Reload 并不会重新执行一遍代码,而是触发 Flutter 重新绘制,并且会保留 Flutter 之前的状态,所以 Hot Reload 也被称为有状态的热重载。
不能使用 Hot Reload 的场景
在理解了 Hot Reload 的原理之后,可以看到 Hot Reload 的使用场景是有一些限制的,接下来我们在看一下不能使用 Hot Reload的 场景:
1. 代码更改会影响 APP 状态的不能使用 Hot Reload
如果你的代码更改会影响 APP 的状态,使得代码更改之后的状态和代码更改之前的状态不一样,那么 Hot Reload 就不会生效,例如:
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(onTap: () => print('T'));
}
}
这段代码,运行 App 之后,将 StatelessWidget 改为 StatefulWidget:
class MyWidget extends StatefulWidget {
@override
State createState() => MyWidgetState();
}
class MyWidgetState extends State { /*...*/ }
因为 Hot Reload 会保留状态,在代码更改之前,MyWidget 是 StatelessWidget ,将 StatelessWidget 改为 StatefulWidget ,如果 Hot Reload 成功,那么 MyWidget 会变成 StatefulWidget ,与它之前的状态就会不兼容的,所以 Hot Reload 是不会成功的。
2. 全局变量( global variables)和静态字段(static fields)的更改不能使用 Hot Reload
在 Flutter 中,全局变量和静态字段被视为状态,因此在 Hot Reload 期间不会重新初始化。
如下的代码:
final sampleTable = [
Table("T1"),
Table("T2"),
Table("T3"),
Table("T4"),
];
运行 App 之后,如果做了如下的更改:
final sampleTable = [
Table("T1"),
Table("T2"),
Table("T3"),
Table("T10"), // 修改这里的值
];
运行 Hot Reload,是不会成功的,所以全局变量和静态字段不能使用 Hot Reload。
3. main() 方法里的更改不能使用 Hot Reload
因为 main() 方法不会因重建窗口小部件树而重新执行,所以更改 main() 方法里的代码,不会在 Hot Reload 之后看到效果。
例如,如下的代码:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(onTap: () => print('tapped'));
}
}
在运行App后,更改如下:
import 'package:flutter/widgets.dart';
void main() {
runApp(const Center(
child: const Text('Hello', textDirection: TextDirection.ltr)));
}
Hot Reload 之后,不会看到任何变化。
- 枚举类型更改为常规的类或者常规的类变为枚举类型也不能使用 HotReload
例如,如下的例子:
enum Color {
red,
green,
blue
}
改为:
class Color {
Color(this.i, this.j);
final int i;
final int j;
}
- 修改通用类型声明也不能使用 HotReload
例如,如下的例子:
class A {
T i;
}
改为:
class A {
T i;
V v;
}
Hot Reload VS Hot Restart
针对上面不能使用 Hot Reload 的情况,就需要使用 Hot Restart。Hot Restart 可以完全重启您的应用程序,但却不用结束调试会话。这个特性可以用来快速调试代码,对于业务代码可能会出现 Hot Restart 失败的情况,这时直接 Hot Reload 也就是直接run就行了,Hot Restart 大部分使用场景还是用来调整UI或者业务代码上小而不改变状态的改动。