众所周知,在Flutter 应用的Debug模式下,当咱们开启【Hot Reload】功能时,不须要在重启应用便可看到最新的代码效果。这种相似于RN、Weex和小程序的热加载功能是如何作到的呢,它背后的原理是什么?小程序
Flutter的热重载(hot reload)功能能够帮助您在无需从新启动应用的状况下快速、轻松地进行测试、构建用户界面、添加功能以及修复错误。 经过将更新后的源代码文件注入正在运行的Dart虚拟机(VM)中来实现热重载。在虚拟机使用新的的字段和函数更新类后,Flutter框架会自动从新构建widget树,以便您快速查看更改的效果。数组
咱们编写一个应用,运行应用程序,而后修改 Flutter APP 工程里的 Dart 代码,而后点击【Hot Reload】按钮开启热重载,以下图所示。
VS Code 开启 Hot Reload 。
当咱们修改Dart代码,点击保存的时候,就会看到界面已经发生了变化,以下图。框架
总结一下,在Flutter中使用热重载须要通过如下几个步骤:less
Hot Reload 成功后,会在 Debug Consol 中看到输出以下相似的消息:ide
Performing hot reload... Reloaded 1 of 448 libraries in 2,777ms.
热重载是指,在不中断 App 正常运行的状况下,动态注入修改后的代码片断。而这一切的背后,离不开 Flutter 所提供的运行时编译能力。为了更好地理解 Flutter 的热重载实现原理,咱们先简单回顾一下 Flutter 编译模式背后的技术吧。函数
JIT(Just In Time),指的是即时编译或运行时编译,在 Debug 模式中使用,能够动态下发和执行代码,启动速度快,但执行性能受运行时编译影响。性能
AOT(Ahead Of Time),指的是提早编译或运行前编译,在 Release 模式中使用,能够为特定的平台生成稳定的二进制代码,执行性能好、运行速度快,但每次执行均需提早编译,开发调试效率低。测试
能够看到,Flutter 提供的两种编译模式中,AOT 是静态编译,即编译成设备可直接执行的二进制码;而 JIT 则是动态编译,即将 Dart 代码编译成中间代码(Script Snapshot),在运行时设备须要 Dart VM 解释执行。ui
而热重载之因此只能在 Debug 模式下使用,是由于 Debug 模式下,Flutter 采用的是 JIT 动态编译(而 Release 模式下采用的是 AOT 静态编译)。JIT 编译器将 Dart 代码编译成能够运行在 Dart VM 上的 Dart Kernel,而 Dart Kernel 是能够动态更新的,这就实现了代码的实时更新功能,原理以下图。this
整体来讲,完成热重载的能够分为扫描工程改动、增量编译、推送更新、代码合并、Widget 重建 5 个步骤。
能够看到,Flutter 提供的热重载在收到代码变动后,并不会让 App 从新启动执行,而只会触发 Widget 树的从新绘制,所以能够保持改动前的状态,这就大大节省了调试复杂交互界面的时间。
好比,咱们须要为一个视图栈很深的页面调整 UI 样式,若采用从新编译的方式,不只须要漫长的全量编译时间,而为了恢复视图栈,也须要重复以前的屡次点击交互,才能从新进入到这个页面查看改动效果。但若是是采用热重载的方式,不只没有编译时间,并且页面的视图栈状态也得以保留,完成热重载以后立刻就能够预览 UI 效果了,至关于进行了局部界面刷新。
Flutter 提供的亚秒级热重载一直是开发者的调试利器。经过热重载,咱们能够快速修改 UI、修复 Bug,无需重启应用便可看到改动效果,从而大大提高了 UI 调试效率。
不过,Flutter 的热重载也有必定的局限性。由于涉及到状态保存与恢复,因此并非全部的代码改动均可以经过热重载来更新。如下是Flutter开发中几个不支持热重载的典型场景:
咱们就具体看看这几种场景的问题,应该如何解决吧!
当代码更改致使编译错误时,热重载会提示编译错误信息。好比下面的例子中,代码中漏写了一个反括号,在使用热重载时,编译器直接报错,以下所示。
Initializing hot reload... Syncing files to device iPhone X... Compiler message: lib/main.dart:84:23: Error: Can't find ')' to match '('. return MaterialApp( ^ Reloaded 1 of 462 libraries in 301ms.
在这种状况下,只需更正上述代码中的错误,就能够继续使用热重载。
当代码更改会影响 Widget 的状态时,会使得热重载先后 Widget 所使用的数据不一致,即应用程序保留的状态与新的更改不兼容。
这时,热重载也是没法使用的。好比下面的代码中,咱们将某个类的定义从 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> { /*...*/ }
当遇到这种状况时,咱们须要重启应用,才能看到更新后的程序的运行效果。
在 Flutter 中,全局变量和静态属性都被视为状态,在第一次运行应用程序时,会将它们的值设为初始化语句的执行结果,所以在热重载期间不会从新初始化。
好比下面的代码中,咱们修改了一个静态 Text 数组的初始化元素。虽然热重载并不会报错,但因为静态变量并不会在热重载以后初始化,所以这个改变并不会产生效果,代码以下。
//改动前 final sampleText = [ Text("T1"), Text("T2"), Text("T3"), Text("T4"), ]; //改动后 final sampleText = [ Text("T1"), Text("T2"), Text("T3"), Text("T10"), //改动点 ];
若是须要更改全局变量和静态属性的初始化语句,须要重启应用才能查看更改效果。
在 Flutter 中,因为热重载以后只会根据原来的根节点从新建立控件树,所以 main 函数的任何改动并不会在热重载后从新执行。因此,若是咱们改动了 main 函数体内的代码,是没法经过热重载看到更新效果的。
//更新前 class MyAPP extends StatelessWidget { @override Widget build(BuildContext context) { return const Center(child: Text('Hello World', textDirection: TextDirection.ltr)); } } void main() => runApp(new MyAPP()); //更新后 void main() => runApp(const Center(child: Text('Hello, 2019', textDirection: TextDirection.ltr)));
因为 main 函数并不会在热重载后从新执行,所以以上改动是没法经过热重载查看更新的。
在热重载时,Flutter 会保存 Widget 的状态,而后重建 Widget。而 initState 方法是 Widget 状态的初始化方法,这个方法里的更改会与状态保存发生冲突,所以热重载后不会产生效果。
例如,在下面的例子中,咱们将计数器的初始值由 10 改成 100,代码以下:
//更改前 class _MyHomePageState extends State<MyHomePage> { int _counter; @override void initState() { _counter = 10; super.initState(); } ... } //更改后 class _MyHomePageState extends State<MyHomePage> { int _counter; @override void initState() { _counter = 100; super.initState(); } ... }
因为这样的改动发生在 initState 方法中,所以没法经过热重载查看更新,咱们须要重启应用,才能看到更改效果。
在 Flutter 中,枚举和泛型也被视为状态,所以对它们的修改也不支持热重载。
好比在下面的代码中,咱们将一个枚举类型改成普通类,并为其增长了一个泛型参数,代码以下。
//更改前 enum Color { red, green, blue } class C<U> { U u; } //更改后 class Color { Color(this.r, this.g, this.b); final int r; final int g; final int b; } class C<U, V> { U u; V v; }
针对上面不能使用 Hot Reload 的状况,就须要使用 Hot Restart。Hot Restart 能够彻底重启您的应用程序,但却不用结束调试会话。
对于Android Studio来讲, 执行 Hot Restart无需 stop操做,再Run 一下,就是 Hot Restart。
对于VS Code 来讲,打开命令面板,输入 Flutter: Hot Restart 或者 直接快捷键 Ctrl+F5,就可使用 Hot Restart。
Flutter 的热重载是基于 JIT 编译模式的代码增量同步。因为 JIT 属于动态编译,可以将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,所以能够经过动态更新中间代码实现增量同步。
热重载的流程能够分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变动后,并不会让 App 从新启动执行,而只会触发 Widget 树的从新绘制,所以能够保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所须要的时间。
另外一方面,因为涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是没法支持的,如改动先后 Widget 状态没法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。
能够发现,热重载提升了调试 UI 的效率,很是适合写界面样式这样须要反复查看修改效果的场景。但因为其状态保存的机制所限,热重载自己也有一些没法支持的边界。
若是你在写业务逻辑的时候,不当心碰到了热重载没法支持的场景,也不须要进行漫长的从新编译加载等待,只要点击位于工程面板左下角的热重启(Hot Restart)按钮,就能够以秒级的速度进行代码从新编译以及程序重启了,一样也很快。