热重载 (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.

要热重载 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 Limitations.

  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:

    Hot reload

    如果您正在使用命令行 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—the value of the counter variable in the above example—is preserved. Your app continues to execute from where it was prior to running the hot reload command. The code updates and execution continues.

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

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 the existing widgets to rebuild. Only code involved in the rebuilding of the widgets are automatically re-executed.

接下来的部分将介绍修改后的代码在热重载后不会再次运行的常见情况。在某些情况下,对 Dart 代码的小改动将确保您能够继续使用热重载。

The next sections describe common situations where the modified code won’t run again after hot reload. In some cases, small changes to the Dart code enable you to continue using hot reload for your app.

编译错误

Compilation errors

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

When a code change introduces a compilation error, hot reload always 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.

先前的状态与新代码并存

Previous state is combined with new code

Flutter 的热重载功能(有时称为有状态热重载)可保留您的应用程序的状态。这种设计允许您能查看最近更改的效果,并且不会丢弃当前状态。例如,如果您的应用需要用户登录,您可以在导航层次结构中下几个级别修改并重新加载页面,而无需重新输入登录凭据。状态保持不变,这通常是期望的行为。

Flutter’s Stateful Hot Reload preserves the state of your app. This design 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.

例如,如果您将某个类的定义从 StatelessWidget 改为 StatefulWidget(或反向更改),则在热重载之后,应用程序的以前状态将保留。但是,该状态可能与新的更改不兼容。

For example, if you modify a class definition from extending StatelessWidget to StatefulWidget (or the reverse), after hot reload the previous state of your app is preserved. However, the state might not be compatible with the new changes.

参考以下代码:

Consider the following code:

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

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

After running the app, if you make the following change:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget> { /*...*/ }

热重载后;控制台将显示类似于以下内容的断言失败的信息:

Then hot reload; the console displays an assertion failure similar to:

MyWidget is not a subtype of StatelessWidget

在这些情况下,需要完全重新启动才可以查看更新后的应用程序.

In these situations, a hot restart is required to see the updated app.

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

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, if you make the following change:

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

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

and then hot reload, 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, if 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:

final bar = foo;

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

To be able 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:

const bar = foo;

或者:

or:

get bar => foo;

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

Read more 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() method.

作为一般规则,如果修改后的代码位于根 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, you might 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 树,热重载后结果没有变化。

但是,如果您在此更改后热重新加载应用程序,main()则不会重新执行,并且使用未更改的实例MyApp作为根小部件重建窗口小部件树。热重载后结果没有明显变化。

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

限制

Limitations

您可能还会遇到极少数根本不支持热重载的情况。这些包括:

You might also encounter the rare cases where hot reload is not supported at all. These include:

  • 更改 initState() 方法,热重载后不会产生效果,需要重新启动。

    Changes on initState() are not reflected by hot reload. A hot restart is required.

  • 枚举类型更改为常规类或常规类更改为枚举类型。例如,如果您更改:

    Enumerated types are changed to regular classes or regular classes are changed to enumerated types. For example, if you change:

    enum Color {
      red,
      green,
      blue
    }
    

    改为:

    to:

    class Color {
      Color(this.i, this.j);
      final int i;
      final int j;
      }
    
  • 泛型类声明被修改。例如,如果您更改:

    Generic type declarations are modified. For example, if you change:

    class A<T> {
      T i;
    }
    

    改为:

    to:

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

在这些情况下,热重载会生成诊断消息,并会失败,也不会提交任何改变。

In these situations, hot reload generates a diagnostic message and fails without committing any changes.

如何实现

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 2 中,这些文件的 Dart 源代码被转换为 内核文件 并发送到移动设备的 Dart VM。

In Dart 2, those libraries’ Dart source code are turned 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 框架触发所有现有的 widgets 和渲染对象的重建/重新布局/重绘。

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