《从零开始自己动手写区块链》适合想学区块链的小白
作者 | xiangzhihong8
责编 | 刘静
出品 | CSDN 博客
众所周知,在Flutter 应用的Debug模式下,当我们开启【Hot Reload】功能时,不需要在重启应用即可看到最新的代码效果。这种类似于RN、Weex和小程序的热加载功能是如何做到的呢,它背后的原理是什么?
![be82c566a56202e6017b844472909288.png](https://i-blog.csdnimg.cn/blog_migrate/db244fa557e51eedc1c818f521fe2043.png)
Flutter的热重载(hot reload)功能可以帮助您在无需重新启动应用的情况下快速、轻松地进行测试、构建用户界面、添加功能以及修复错误。通过将更新后的源代码文件注入正在运行的Dart虚拟机(VM)中来实现热重载。在虚拟机使用新的的字段和函数更新类后,Flutter框架会自动重新构建widget树,以便您快速查看更改的效果。
我们编写一个应用,运行应用程序,然后修改 Flutter APP 工程里的 Dart 代码,然后点击【Hot Reload】按钮开启热重载,如下图所示。
![0d0b74a4019e0cf1fc564a32cb20b9a0.png](https://i-blog.csdnimg.cn/blog_migrate/336b16967f3a56f5bd41cddc63de73a2.jpeg)
- 连接真机或虚拟机,运行 Flutter APP,且必须以 Debug 模式启动。因为只有 Debug 模式才能使用 Hot Reload;
- 修改 Flutter APP 工程里的 Dart 代码,但并不是所有 Dart 代码的修改都可以使用 Hot Reload,有一些情况下Hot Reload 并不能生效,只能使用 Hot Restart(重新启动);
- 使用快捷键 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.
![00b29326c5480e652fa6897b586f7421.png](https://i-blog.csdnimg.cn/blog_migrate/8d73a04e66ecbe81654baf05d5bfeb90.png)
热重载
热重载是指,在不中断 App 正常运行的情况下,动态注入修改后的代码片段。而这一切的背后,离不开 Flutter 所提供的运行时编译能力。为了更好地理解 Flutter 的热重载实现原理,我们先简单回顾一下 Flutter 编译模式背后的技术吧。
JIT
JIT(Just In Time),指的是即时编译或运行时编译,在 Debug 模式中使用,可以动态下发和执行代码,启动速度快,但执行性能受运行时编译影响。
AOT
AOT(Ahead Of Time),指的是提前编译或运行前编译,在 Release 模式中使用,可以为特定的平台生成稳定的二进制代码,执行性能好、运行速度快,但每次执行均需提前编译,开发调试效率低。
![4d644d5a91bc949d00fdf98613193f38.png](https://i-blog.csdnimg.cn/blog_migrate/7492d2c31bdad4c35bbc378227d890b9.png)
而热重载之所以只能在 Debug 模式下使用,是因为 Debug 模式下,Flutter 采用的是 JIT 动态编译(而 Release 模式下采用的是 AOT 静态编译)。JIT 编译器将 Dart 代码编译成可以运行在 Dart VM 上的 Dart Kernel,而 Dart Kernel 是可以动态更新的,这就实现了代码的实时更新功能,原理如下图。
![3cadd2218a2b29fc932f53c5ed8c9dba.png](https://i-blog.csdnimg.cn/blog_migrate/09ca31eb25ebe8ee96c540da6d63416f.png)
工程改动。热重载模块会逐一扫描工程中的文件,检查是否有新增、删除或者改动,直到找到在上次编译之后,发生变化的 Dart 代码;
**增量编译。**热重载模块会将发生变化的 Dart 代码,通过编译转化为增量的 Dart Kernel 文件;
推送更新。热重载模块将增量的 Dart Kernel 文件通过 HTTP 端口,发送给正在移动设备上运行的 Dart VM;
代码合并。Dart VM 会将收到的增量 Dart Kernel 文件,与原有的 Dart Kernel 文件进行合并,然后重新加载新的Dart Kernel 文件;
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 时,热重载就会直接报错,如下所示。
//改动前
当遇到这种情况时,我们需要重启应用,才能看到更新后的程序的运行效果。
全局变量和静态属性的更改
在 Flutter 中,全局变量和静态属性都被视为状态,在第一次运行应用程序时,会将它们的值设为初始化语句的执行结果,因此在热重载期间不会重新初始化。
比如下面的代码中,我们修改了一个静态 Text 数组的初始化元素。虽然热重载并不会报错,但由于静态变量并不会在热重载之后初始化,因此这个改变并不会产生效果,代码如下。
//改动前
如果需要更改全局变量和静态属性的初始化语句,需要重启应用才能查看更改效果。
main 方法里代码更改
在 Flutter 中,由于热重载之后只会根据原来的根节点重新创建控件树,因此 main 函数的任何改动并不会在热重载后重新执行。所以,如果我们改动了 main 函数体内的代码,是无法通过热重载看到更新效果的。
//更新前
由于 main 函数并不会在热重载后重新执行,因此以上改动是无法通过热重载查看更新的。
initState 方法里代码更改
在热重载时,Flutter 会保存 Widget 的状态,然后重建 Widget。而 initState 方法是 Widget 状态的初始化方法,这个方法里的更改会与状态保存发生冲突,因此热重载后不会产生效果。
例如,在下面的例子中,我们将计数器的初始值由 10 改为 100,代码如下:
//更改前
由于这样的改动发生在 initState 方法中,因此无法通过热重载查看更新,我们需要重启应用,才能看到更改效果。
枚举和泛型类型更改
在 Flutter 中,枚举和泛型也被视为状态,因此对它们的修改也不支持热重载。
比如在下面的代码中,我们将一个枚举类型改为普通类,并为其增加了一个泛型参数,代码如下。
//更改前
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。
![1a68fa932b97f35fc6e3035cb5ad1a75.png](https://i-blog.csdnimg.cn/blog_migrate/f1f1302f7db6e8a50b8eb284561c76b6.png)
Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。
热重载的流程可以分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所需要的时间。
另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。
可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。
如果你在写业务逻辑的时候,不小心碰到了热重载无法支持的场景,也不需要进行漫长的重新编译加载等待,只要点击位于工程面板左下角的热重启(Hot Restart)按钮,就可以以秒级的速度进行代码重新编译以及程序重启了,同样也很快。
版权声明:本文为CSDN博主「xiangzhihong8」的原创文章。 想为博主点赞? 想要请教博主? 扫描下方二维码,快速获取与博主直面沟通的方式吧!![2edfd9ca6f507a79e3da3965deb56b96.png](https://i-blog.csdnimg.cn/blog_migrate/ce7fc01e3f6ea03d6e5fb9db2216981a.jpeg)
☞90 后技术宅研发 Magi 一夜爆红,新一代知识化结构搜索新时代来了?
☞ i 智慧 | 为云而生、多快好省,这就是星星海的style!☞GitHub标星近10万:只需5秒音源,这个网络就能实时“克隆”你的声音
☞支付宝首次在双11使用区块链技术的背后,新零售正在被重塑……
![beed261f04ba802b6107eabbccb412c4.png](https://i-blog.csdnimg.cn/blog_migrate/e2b54e7dd7b67a036b0e304c85a8db19.png)