Hot reload

Flutter的热重载功能可帮助您快速轻松地进行实验,构建UI,添加功能并修复错误. 通过将更新的源代码文件注入到正在运行的Dart虚拟机(VM)中,可以进行热重装. VM使用新版本的字段和函数更新类后,Flutter框架会自动重建小部件树,使您可以快速查看更改的效果.

要热重新加载Flutter应用,请执行以下操作:

  1. 从受支持的Flutter编辑器或终端窗口中运行该应用程序. 物理设备或虚拟设备都可以成为目标. 只有处于调试模式的Flutter应用才能被热加载.
  2. 修改项目中的Dart文件之一. 大多数类型的代码更改都可以热加载. 有关需要热重启的更改的列表,请参见" 限制" .
  3. 如果您在支持Flutter IDE工具的IDE /编辑器中工作,请选择全部保存cmd-s / ctrl-s ),或单击工具栏上的Hot Reload按钮:

    Hot reload

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

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

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

应用程序会更新以反映您的更改,并且会保留应用程序的当前状态(上例中的counter变量的值). 您的应用将从运行hot reload命令之前的位置继续执行. 代码更新并继续执行.

仅当更改后再次运行修改后的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

当代码更改引入编译错误时,热重装总是生成类似于以下内容的错误消息:

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 ')'
    );
    ^

在这种情况下,只需更正Dart代码指定行上的错误即可继续使用热重装.

Previous state is combined with new code

Flutter的有状态热重载可保留您应用的状态. 此设计使您可以仅查看最新更改的效果,而不会丢弃当前状态. 例如,如果您的应用程序要求用户登录,则可以在导航层次结构中向下几个级别修改和热重新加载页面,而无需重新输入登录凭据. 保持状态,这通常是所需的行为.

如果代码更改影响了应用程序的状态(或其依赖项),则应用程序必须使用的数据可能与从头开始执行的数据不完全一致. 在热重新加载与热重新启动之后,结果可能是不同的行为.

例如,如果您将类定义从StatelessWidget扩展为StatefulWidget(或相反),则在热重新加载后,将保留应用程序的先前状态. 但是,状态可能与新更改不兼容.

考虑以下代码:

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

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

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

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

然后热装; 控制台显示断言失败,类似于:

MyWidget is not a subtype of StatelessWidget

在这些情况下,需要热重启才能查看更新的应用程序.

Recent code change is included but app state is excluded

在Dart中, 静态字段被延迟初始化 . 这意味着您第一次运行Flutter应用并读取静态字段时,会将其设置为初始化程序求值的任何值. 全局变量和静态字段被视为状态,因此在热重载期间不会重新初始化.

如果更改全局变量和静态字段的初始化程序,则必须完全重新启动才能查看更改. 例如,考虑以下代码:

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

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

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

然后热重载,更改不会反映出来.

相反,在以下示例中:

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

首次运行该应用程序将打印11 . 然后,如果您进行以下更改:

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

虽然对const字段值const更改总是热重新加载,但不会重新运行静态字段初始化程序. 从概念上讲, const字段被视为别名而不是状态.

当一组更改需要热重启才能生效时,Dart VM会检测到初始化程序更改并进行标记. 在上面的示例中,大多数初始化工作都会触发标记机制,但对于以下情况则不会触发:

final bar = foo;

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

const bar = foo;

or:

get bar => foo;

详细了解Dart中constfinal关键字之间区别 .

Recent UI change is excluded

即使热重装操作看起来成功并且没有生成异常,某些代码更改在刷新的UI中也可能不可见. 更改应用程序的main()方法后,此行为很常见.

通常,如果修改后的代码位于根窗口小部件的build方法的下游,则热重装将按预期进行. 但是,如果由于重建小部件树而不会重新执行修改后的代码,那么在热重载后您将看不到它的效果.

例如,考虑以下代码:

import 'package:flutter/material.dart';

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

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

运行此应用程序后,您可以按以下方式更改代码:

import 'package:flutter/widgets.dart';

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

通过热重启,该程序从头开始,执行新版本的main() ,并构建一个显示文本Hello的小部件树.

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

Limitations

您可能还会遇到很少完全不支持热重装的情况. 这些包括:

  • initState()更改不会通过热重载反映出来. 需要热重启.

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

    enum Color {
      red,
      green,
      blue
    }
    

    to:

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

    class A<T> {
      T i;
    }
    

    to:

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

在这些情况下,热重装将生成诊断消息,并且在未提交任何更改的情况下将失败.

How it works

调用热重装时,主机将查看自上次编译以来的已编辑代码. 重新编译以下库:

  • 任何代码更改的库
  • 应用程序的主库
  • 主库中的库导致受影响的库

在Dart 2中,这些库的Dart源代码被转换为内核文件 ,并发送到移动设备的Dart VM.

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

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