热重载 (Hot reload)

Flutter 的热重载功能可帮助您在无需重新启动应用程序的情况下快速、轻松地测试、构建用户界面、添加功能以及修复错误。通过将更新的源代码文件注入到正在运行的 Dart 虚拟机(VM) 来实现热重载。在虚拟机使用新的字段和函数更新类之后, Flutter 框架会自动重新构建 widget 树,以便您可以快速查看更改的效果。

Flutter’s hot reload feature helps you quickly and easily experiment, build UIs, add features, and fix bugs. Hot reload works by injecting updated source code files into the running Dart Virtual Machine (VM). After the VM updates classes with the new versions of fields and functions, the Flutter framework automatically rebuilds the widget tree, allowing you to quickly view the effects of your changes.

如何热重载:

How to perform a hot reload

热重载 Flutter 应用:

To hot reload a Flutter app:

  1. 在支持 Flutter 编辑器 或终端窗口运行应用程序,物理机或虚拟器都可以。 Flutter 应用程序只有在调试模式下才能被热重载。

    Run the app from a supported Flutter editor or a terminal window. Either a physical or virtual device can be the target. Only Flutter apps in debug mode can be hot reloaded.

  2. 修改项目中的一个Dart文件。大多数类型的代码更改可以热重载;一些需要重新启动应用程序的更改列表,请参阅 特别情况

    Modify one of the Dart files in your project. Most types of code changes can be hot reloaded; for a list of changes that require a hot restart, see Special cases.

  3. 如果您在支持 Flutter IDE 工具的 IDE /编辑器中工作,请选择 Save All (cmd-s/ctrl-s),或单击工具栏上的 Hot Reload 按钮。

    If you’re working in an IDE/editor that supports Flutter’s IDE tools, select Save All (cmd-s/ctrl-s), or click the hot reload button on the toolbar.

    如果您正在使用命令行 flutter run 运行应用程序,请在终端窗口输入 r

    If you’re running the app at the command line using flutter run, enter r in the terminal window.

成功执行热重载后,您将在控制台中看到类似于以下内容的消息:

After a successful hot reload operation, you’ll see a message in the console similar to:

Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.

应用程序更新以反映您的更改,并且应用程序的当前状态将保留。您的应用程序将继续从之前运行热重载命令的位置开始执行。代码被更新并继续执行。

The app updates to reflect your change, and the current state of the app is preserved. Your app continues to execute from where it was prior to running the hot reload command. The code updates and execution continues.

Android Studio UI
Controls for run, run debug, hot reload, and hot restart in Android Studio

只有修改后的 Dart 代码再次运行时,代码更改才会产生可见效果。具体来说,热重载会导致所有现有的 widgets 重新构建。只有与 widgets 重新构建相关的代码才会自动重新执行。 main() and initState() 方法则不会再次运行。

A code change has a visible effect only if the modified Dart code is run again after the change. Specifically, a hot reload causes all of the existing widgets to rebuild. Only code involved in the rebuilding of the widgets is automatically re-executed. The main() and initState() functions, for example, are not run again.

特别情况

Special cases

下面的部分会描述一些热重载的特别的情况。在某些情况下,对 Dart 代码的小改动将确保您能够继续使用热重载。在其他情况下,需要热重启或完全重启。

The next sections describe specific scenarios that involve hot reload. In some cases, small changes to the Dart code enable you to continue using hot reload for your app. In other cases, a hot restart, or a full restart is needed.

应用被杀死

An app is killed

热重载会在应用被杀死之后断掉。比如一直在后台运行的应用(会被系统杀死)。

Hot reload can break when the app is killed. For example, if the app was in the background for too long.

Compilation errors

编译错误

当代码更改导致编译错误时,热重载会生成类似于以下内容的错误消息:

When a code change introduces a compilation error, hot reload generates an error message similar to:

Hot reload was rejected:
'/Users/obiwan/Library/Developer/CoreSimulator/Devices/AC94F0FF-16F7-46C8-B4BF-218B73C547AC/data/Containers/Data/Application/4F72B076-42AD-44A4-A7CF-57D9F93E895E/tmp/ios_testWIDYdS/ios_test/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here
  Widget build(BuildContext context) {
                                     ^
'/Users/obiwan/Library/Developer/CoreSimulator/Devices/AC94F0FF-16F7-46C8-B4BF-218B73C547AC/data/Containers/Data/Application/4F72B076-42AD-44A4-A7CF-57D9F93E895E/tmp/ios_testWIDYdS/ios_test/lib/main.dart': error: line 33 pos 5: unbalanced ')'
    );
    ^

在这种情况下,只需更正上述代码的错误,即可以继续使用热重载。

In this situation, simply correct the errors on the specified lines of Dart code to keep using hot reload.

CupertinoTabView’s builder

Hot reload won’t apply changes made to a builder of a CupertinoTabView. For more information, see Issue 43574.

Enumerated types

Hot reload doesn’t work when enumerated types are changed to regular classes or regular classes are changed to enumerated types.

For example:

Before the change:

enum Color {
  red,
  green,
  blue
}

After the change:

class Color {
  Color(this.i, this.j);
  final int i;
  final int j;
}

字体修改

Changing fonts

Hot reload supports changing assets, for the most part. However, if you change fonts, you’ll need to hot restart.

Generic types

Hot reload won’t work when generic type declarations are modified. For example, the following won’t work:

Before the change:

class A<T> {
  T i;
}

After the change:

class A<T, V> {
  T i;
  V v;
}

Native code

If you’ve changed native code (such as Kotlin, Java, Swift, or Objective-C), you must perform a full restart (stop and restart the app) to see the changes take effect.

Previous state is combined with new code

Flutter’s stateful hot reload preserves the state of your app. This approach enables you to view the effect of the most recent change only, without throwing away the current state. For example, if your app requires a user to log in, you can modify and hot reload a page several levels down in the navigation hierarchy, without re-entering your login credentials. State is kept, which is usually the desired behavior.

If code changes affect the state of your app (or its dependencies), the data your app has to work with might not be fully consistent with the data it would have if it executed from scratch. The result might be different behavior after hot reload versus a hot restart.

Recent code change is included but app state is excluded

代码发生更改但应用程序的状态没有改变

在 Dart 中,静态字段会被惰性初始化。这意味着第一次运行 Flutter 应用程序并读取静态字段时,会将静态字段的值设为其初始表达式的结果。全局变量和静态字段都被视为状态,因此在热重载期间不会重新初始化。

In Dart, static fields are lazily initialized. This means that the first time you run a Flutter app and a static field is read, it is set to whatever value its initializer was evaluated to. Global variables and static fields are treated as state, and are therefore not reinitialized during hot reload.

如果更改全局变量和静态字段的初始化语句,则需要完全重启以查看更改。例如,参考以下代码:

If you change initializers of global variables and static fields, a full restart is necessary to see the changes. For example, consider the following code:

final sampleTable = [
  Table("T1"),
  Table("T2"),
  Table("T3"),
  Table("T4"),
];

运行应用程序后,如果进行以下更改:

After running the app, you make the following change:

final sampleTable = [
  Table("T1"),
  Table("T2"),
  Table("T3"),
  Table("T10"),    // modified
];

热重载后,这个改变并没有产生效果。

You hot reload, but the change is not reflected.

相反,在下面示例中:

Conversely, in the following example:

const foo = 1;
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

第一次运行应用程序会打印 11。然后,如果您进行以下更改:

Running the app for the first time prints 1 and 1. Then, you make the following change:

const foo = 2;    // modified
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

热重载后,现在打印出 21。虽然对 const 定义的字段值的更改始终会重新加载,但不会重新运行静态字段的初始化语句。从概念上讲,const 字段被视为别名而不是状态。

While changes to const field values are always hot reloaded, the static field initializer is not rerun. Conceptually, const fields are treated like aliases instead of state.

Dart VM 在一组更改需要完全重启才能生效时,会检测初始化程序更改和标志。在上面的示例中,大部分初始化工作都会触发标记机制,但不适用于以下情况:

The Dart VM detects initializer changes and flags when a set of changes needs a hot restart to take effect. The flagging mechanism is triggered for most of the initialization work in the above example, but not for cases like the following:

final bar = foo;

为了能够更改 foo 并在热重载后查看更改,应该将字段重新用 const 定义或使用 getter 来返回值,而不是使用 final。例如下面的解决方案应该都可以使用:

To update foo and view the change after hot reload, consider redefining the field as const or using a getter to return the value, rather than using final. For example, either of the following solutions work:

const bar = foo;

或者:

or:

const bar = foo;    // Convert foo to a const...
get bar => foo;     // ...or provide a getter.

了解更多 Dart 中关于 const 和 final 关键字的区别.

For more information, read about the differences between the const and final keywords in Dart.

Recent UI change is excluded

用户界面没有改变

即使热重载操作看起来成功了并且没有抛出异常,但某些代码更改可能在更新的 UI 中不可见。这种行为在更改应用程序的 main() 方法后很常见。

Even when a hot reload operation appears successful and generates no exceptions, some code changes might not be visible in the refreshed UI. This behavior is common after changes to the app’s main() or initState() methods.

作为一般规则,如果修改后的代码位于根 widget 的构建方法的下游,则热重载将按预期运行。但是,如果修改后的代码不会因重新构建 widget 树而重新执行的话,那么在热重载后您将看不到它的效果。

As a general rule, if the modified code is downstream of the root widget’s build() method, then hot reload behaves as expected. However, if the modified code won’t be re-executed as a result of rebuilding the widget tree, then you won’t see its effects after hot reload.

例如,参考以下代码:

For example, consider the following code:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return GestureDetector(onTap: () => print('tapped'));
  }
}

运行应用程序后,你可能会像如下示例更改代码:

After running this app, change the code as follows:

import 'package:flutter/widgets.dart';

void main() {
  runApp(const Center(
      child: const Text('Hello', textDirection: TextDirection.ltr)));
}

完全重启后,程序会从头开始执行新的 main() 方法,并构建一个 widget 树来显示文本 Hello

With a hot restart, the program starts from the beginning, executes the new version of main(), and builds a widget tree that displays the text Hello.

但是,如果您在更改后是通过热重载运行,main() 方法则不会重新执行,并且会使用未修改的 MyApp 实例作为根 widget 树来构建新的 widget 树,热重载后结果没有变化。

However, if you hot reload the app after this change, main() and initState() are not re-executed, and the widget tree is rebuilt with the unchanged instance of MyApp as the root widget. This results in no visible change after hot reload.

如何实现

How it works

调用热重载时,主机会查看自上次编译以来编辑的代码。重新编译以下文件:

When hot reload is invoked, the host machine looks at the edited code since the last compilation. The following libraries are recompiled:

  • 任何有代码更改的文件;

    Any libraries with changed code

  • 应用程序的主入口文件。

    The application’s main library

  • 受主入口文件影响的文件。

    The libraries from the main library leading to affected libraries

这些库中的源代码被编译为 内核文件,并发送到移动设备的 Dart VM 中。

The source code from those libraries is compiled into kernel files and sent to the mobile device’s Dart VM.

Dart VM 重新加载新内核文件中的所有文件。到目前为止,没有重新执行代码。

The Dart VM re-loads all libraries from the new kernel file. So far no code is re-executed.

然后,热重载机制使 Flutter 框架触发所有现有的 widget 和渲染对象的重建/重新布局/重绘。

The hot reload mechanism then causes the Flutter framework to trigger a rebuild/re-layout/repaint of all existing widgets and render objects.