众所周知,在Flutter 应用的Debug模式下,当我们开启【Hot Reload】功能时,不需要在重启应用即可看到最新的代码效果。这种类似于RN、Weex和小程序的热加载功能是如何做到的呢,它背后的原理是什么?

基本使用方法

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

我们编写一个应用,运行应用程序,然后修改 Flutter APP 工程里的 Dart 代码,然后点击【Hot Reload】按钮开启热重载,如下图所示。

VS Code 开启 Hot Reload 。

当我们修改Dart代码,点击保存的时候,就会看到界面已经发生了变化,如下图。

总结一下,在Flutter中使用热重载需要经过以下几个步骤:

  1. 连接真机或虚拟机,运行 Flutter APP,且必须以 Debug 模式启动。因为只有 Debug 模式才能使用 Hot Reload。
  2. 修改 Flutter APP 工程里的 Dart 代码,但并不是所有 Dart 代码的修改都可以使用 Hot Reload,有一些情况下Hot Reload 并不能生效,只能使用 Hot Restart(重新启动)。
  3. 使用快捷键 ctrl+s(Windows、Linux)或者 cmd+s(MacOS),或者点击 Hot Reload 的按钮,就完成了Hot Reload 的操作,

Hot Reload 成功后,会在 Debug Consol 中看到输出如下类似的消息:

Performing hot reload...
Reloaded 1 of 448 libraries in 2,777ms.

工作原理

热重载

热重载是指,在不中断 App 正常运行的情况下,动态注入修改后的代码片段。而这一切的背后,离不开 Flutter 所提供的运行时编译能力。为了更好地理解 Flutter 的热重载实现原理,我们先简单回顾一下 Flutter 编译模式背后的技术吧。

JIT

JIT(Just In Time),指的是即时编译或运行时编译,在 Debug 模式中使用,可以动态下发和执行代码,启动速度快,但执行性能受运行时编译影响。

AOT

AOT(Ahead Of Time),指的是提前编译或运行前编译,在 Release 模式中使用,可以为特定的平台生成稳定的二进制代码,执行性能好、运行速度快,但每次执行均需提前编译,开发调试效率低。

可以看到,Flutter 提供的两种编译模式中,AOT 是静态编译,即编译成设备可直接执行的二进制码;而 JIT 则是动态编译,即将 Dart 代码编译成中间代码(Script Snapshot),在运行时设备需要 Dart VM 解释执行。

而热重载之所以只能在 Debug 模式下使用,是因为 Debug 模式下,Flutter 采用的是 JIT 动态编译(而 Release 模式下采用的是 AOT 静态编译)。JIT 编译器将 Dart 代码编译成可以运行在 Dart VM 上的 Dart Kernel,而 Dart Kernel 是可以动态更新的,这就实现了代码的实时更新功能,原理如下图。

总体来说,完成热重载的可以分为扫描工程改动、增量编译、推送更新、代码合并、Widget 重建 5 个步骤。

  1. 工程改动。热重载模块会逐一扫描工程中的文件,检查是否有新增、删除或者改动,直到找到在上次编译之后,发生变化的 Dart 代码。
  2. 增量编译。热重载模块会将发生变化的 Dart 代码,通过编译转化为增量的 Dart Kernel 文件。
  3. 推送更新。热重载模块将增量的 Dart Kernel 文件通过 HTTP 端口,发送给正在移动设备上运行的 Dart VM。
  4. 代码合并。Dart VM 会将收到的增量 Dart Kernel 文件,与原有的 Dart Kernel 文件进行合并,然后重新加载新的Dart Kernel 文件。
  5. Widget 重建。在确认 Dart VM 资源加载成功后,Flutter 会将其 UI 线程重置,通知 Flutter Framework 重建 Widget。

可以看到,Flutter 提供的热重载在收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,这就大大节省了调试复杂交互界面的时间。

比如,我们需要为一个视图栈很深的页面调整 UI 样式,若采用重新编译的方式,不仅需要漫长的全量编译时间,而为了恢复视图栈,也需要重复之前的多次点击交互,才能重新进入到这个页面查看改动效果。但如果是采用热重载的方式,不仅没有编译时间,而且页面的视图栈状态也得以保留,完成热重载之后马上就可以预览 UI 效果了,相当于进行了局部界面刷新。

不支持热重载的场景

Flutter 提供的亚秒级热重载一直是开发者的调试利器。通过热重载,我们可以快速修改 UI、修复 Bug,无需重启应用即可看到改动效果,从而大大提升了 UI 调试效率。

不过,Flutter 的热重载也有一定的局限性。因为涉及到状态保存与恢复,所以并不是所有的代码改动都可以通过热重载来更新。以下是Flutter开发中几个不支持热重载的典型场景:

  • 代码出现编译错误;
  • Widget 状态无法兼容;
  • 全局变量和静态属性的更改;
  • main 方法里的更改;
  • initState 方法里的更改;
  • 枚举和泛类型更改。

我们就具体看看这几种场景的问题,应该如何解决吧!

代码出现编译错误

当代码更改导致编译错误时,热重载会提示编译错误信息。比如下面的例子中,代码中漏写了一个反括号,在使用热重载时,编译器直接报错,如下所示。

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 的状态时,会使得热重载前后 Widget 所使用的数据不一致,即应用程序保留的状态与新的更改不兼容。

这时,热重载也是无法使用的。比如下面的代码中,我们将某个类的定义从 StatelessWidget 改为 StatefulWidget 时,热重载就会直接报错,如下所示。

//改动前
class MyWidget extends StatelessWidget {Widget build(BuildContext context) {return GestureDetector(onTap: () => print('T'));}
}//改动后
class MyWidget extends StatefulWidget {@overrideState<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"),    //改动点
];

如果需要更改全局变量和静态属性的初始化语句,需要重启应用才能查看更改效果。

main 方法里代码更改

在 Flutter 中,由于热重载之后只会根据原来的根节点重新创建控件树,因此 main 函数的任何改动并不会在热重载后重新执行。所以,如果我们改动了 main 函数体内的代码,是无法通过热重载看到更新效果的。

//更新前
class MyAPP extends StatelessWidget {
@overrideWidget 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 函数并不会在热重载后重新执行,因此以上改动是无法通过热重载查看更新的。

initState 方法里代码更改

在热重载时,Flutter 会保存 Widget 的状态,然后重建 Widget。而 initState 方法是 Widget 状态的初始化方法,这个方法里的更改会与状态保存发生冲突,因此热重载后不会产生效果。

例如,在下面的例子中,我们将计数器的初始值由 10 改为 100,代码如下:

//更改前
class _MyHomePageState extends State<MyHomePage> {int _counter;@overridevoid initState() {_counter = 10;super.initState();}...
}//更改后
class _MyHomePageState extends State<MyHomePage> {int _counter;@overridevoid 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 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)按钮,就可以以秒级的速度进行代码重新编译以及程序重启了,同样也很快。

运行代码后总是会出现很多的的debug [main请问如何解决_Flutter的Hot Reload是如何做到的...相关推荐

  1. 谷歌浏览器java不能启动,运行代码后,Chrome浏览器不会启动。有什么问题?

    运行此代码后,Chrome浏览器将不再提供午餐. 我使用的是Chrome浏览器v.71.0.3578.98(官方版本),(32位) 以及Chrome驱动程序版本2.42 public class Ma ...

  2. mysql执行语句出来全是问号_为什么mysql运行代码后结果栏中的中文全部变成问号?...

    将my.ini文件中的默认字符集改为gb2312即可 具体的办法是修改其中的[mysqld] 里添加一行: default-character-set=gb2312 然后重启服务,不过只对修改后的数据 ...

  3. 运行代码后出现Process finished with exit code 0是为什么?

    意味着你的程序正常执行完毕并退出. 可以科普一下exit code,在大部分编程语言中都适用: exit code 0 表示程序执行成功,正常退出 例如: exit code 1 表示程序执行执行过程 ...

  4. R语言-运行作图后不显示图片

    1. R官方编译器 通常直接在R语言的官方界面运行命令行或者脚本程序,一般不会存在运行后不出图的问题. 如果没有出图,一般情况下可能的原因是: 可能是你的代码写错了 可能你的packages需要更高版 ...

  5. **农商手机银行app更新代码后访问速度非常慢

    问题描述: **农商手机银行app更新代码后访问速度非常慢,几乎每个请求都非常的慢. 解决思路: 由于是远程支持解决,第一时间要了最近更新上去的代码,代码进行了review, 只是一些简单的业务代码更 ...

  6. 运行 Java、Python、Go 等 25 种代码后,发现性能最强的竟然是它!

    本文通过一道程序面试题,使用不同的编程语言来实现,检验每种语言的简单版本与优化后版本的运行速度分别是多少,横向对比 Python.Go.C++.C.Rust 等编程语言的性能, 作者 | Ben Ho ...

  7. VsCode配置Python开发环境后运行代码会报错“无法加载文件 D:\Code\xxx\poetry-demo\.venv\Scripts\Activate.ps1”

    问题描述:在VsCode中配置Python开发环境后运行代码会报错"无法加载文件 D:\Code\xxx\poetry-demo\.venv\Scripts\Activate.ps1&quo ...

  8. 解决CPLEX安装后无法运行代码,报错乱码(错误显示:ÔËÐÐÅäÖá°配置 1¡±²»´æÔڡ£)

    解决CPLEX安装后无法运行代码,报错乱码(错误显示:ÔËÐÐÅäÖá°配置 1¡±²»´æÔڡ£) CPLEX错误显示如下: 解决方法: 重命名"配置 1"为"Conf ...

  9. 下载python后怎么运行代码,怎样下载python的编译器

    python怎么运行 python运行的具体步骤:工具:戴尔电脑. 1.将下载的python解释器的路径添加到环境变量中,之后在命令行中输入python会出现如下的显示版本号的信息,之后就可在命令行写 ...

最新文章

  1. TOMCAT为什么打破双亲委派的类加载模型
  2. 首席信息官利用AI提升自身地位的三种方法
  3. 为什么大家都不戳破深度学习的本质?!
  4. NHibernate使用时,不能返回自己的异常的解决办法
  5. 分布式系统服务器要求,浅谈分布式系统
  6. 数据结构之二叉树的遍历
  7. 在Ubuntu Server上使用vtk处理体数据,直接得到渲染结果图片避免显示窗口
  8. Leetcode每日一题:167.two-sum-ii-input-array-is-sorted(两数之和Ⅱ-输入有序数组)
  9. 游戏开发之使用类封装双链表数据结构及双链表迭代器--第二版(C++基础)
  10. 针对unicode对象---检测字符串是否只由数字组成
  11. 【SpringBoot】整合jdbc
  12. 一个不知名前辈的创业史(血泪)
  13. CU的递归划分详细介绍
  14. JDBC 和数据库连接池
  15. 查看电脑开机关机记录
  16. 39、C++定义一个类,实现向量的加减运算
  17. 赵丽颖冯绍峰官宣 | 微博服务器瘫痪!运维:该拿什么拯救我?
  18. 机器学习中的各种损失函数(Hinge loss,交叉熵,softmax)
  19. 【Element-ui】el-table大数据量渲染卡顿问题
  20. [SCI][计算机视觉][图像处理]一二三四区期刊(自用)

热门文章

  1. java单点登录强制下线_实现单点登录并强制对方下线
  2. 7.Spring Security 退出登录
  3. 大佬告诉你JavaScript面试题大全之基础面试题(附答案)
  4. Html5的页面基本结构标签,HTML5:简介和文档基本结构
  5. 计算机组装与维修单元卷,计算机组装与维修期中考试试卷及答案
  6. 怎么通过ip连接oracle,Oracle 无法通过IP连接问题
  7. php 导出excel 特殊字符,export 导出的excel sheet名字包含特殊字符
  8. mysql 分库分表 建表_【分库分表】sharding-jdbc实践—分库分表入门
  9. python大众点评网应该涉及哪些参考文献及其出版社_python小练习(052):爬取大众点评网美食版块+数据库储存+大数据分析(二)...
  10. mysql的altertable_mysql 的 alter table 操作性能小提示