likes
comments
collection
share

Flutter-热重载原理解析

作者站长头像
站长
· 阅读数 8

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

工作原理

JIT 和 AOT 的区别:

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

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

可以看出,这两种编译模式,AOT 是静态编译,最终产物编译成可直接执行的机器码,而 JIT 则是动态编译,dart 代码编译成的是中间代码,在程序运行的时候通过 Dart VM 解释运行。

Flutter-热重载原理解析

热重载的步骤:

  • 工程改动:热重载Server会逐一扫描工程中的文件,检查是否有新增、删除或者改动,直到找到在上次编译之后,发生变化的 Dart 代码。
  • 增量编译:热重载模块会将发生变化的 Dart 代码,通过编译转化为增量的 Dart Kernel 文件。
  • 推送更新:热重载Server将增量的 Dart Kernel 文件通过 RPC 协议,发送给正在手机上运行的 Dart VM
  • 代码合并:Dart VM 会将收到的增量 Dart Kernel 文件,与原有的 Dart Kernel 文件进行合并,然后重新加载新的 Dart Kernel 文件。
  • Widget增量渲染:在确认 Dart VM 资源加载成功后,Flutter 会将其 UI 线程重置,通知 flutter.framework 重建 Widget。

不支持热重载的场景

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

热重载的源码解析

启动流程解析

首先创建一个 flutter 项目 hotload_demo,找到flutter->packages->flutter_tools,通过 Android Studio 打开,然后打开Edit Configurations,把刚才创建的工程 hotload_demo配置Working directoryProgram arguments先配置一个参数 run

Flutter-热重载原理解析

  1. 开始调试运行项目,在 bin->flutter_tools.dart 中的main函数打上断点,可以看到参数有传入进来

Flutter-热重载原理解析

  1. 进入 main 函数,发现在对传进来的参数进行分析,然后进行一些回调处理

Flutter-热重载原理解析

  1. 下面发现调用了 runner.run(args) 方法

Flutter-热重载原理解析

  1. run 方法调用完毕后,会调用run.dart里面的 runCommand() 方法

Flutter-热重载原理解析

  1. 在 runCommand() 方法里面会找到我们的设备,这里可以看出设备就是我运行的模拟器设备 iPhone 12 pro Max

Flutter-热重载原理解析

  1. 继续调试发现在终端打印了 Running Xcode build..., xcode build done, 这些是在mac.dart里面处理的

Flutter-热重载原理解析

调起一些指令给Xcode编译执行

Flutter-热重载原理解析

  1. 在 runcommand() 方法里面,我们就可以看到开始注册和启动终端

Flutter-热重载原理解析

  1. setupTerminal() 方法里面就会打印那些帮助信息,什么r 、R 等等,并且下面看到也开始监听终端的输入了

Flutter-热重载原理解析

  1. 可以看下 printHelp 方法的实现,就是输出打印了那些帮助信息

Flutter-热重载原理解析

  1. 发现终端最下面输出了两个 URL ,上面这个是dart虚拟机的,下面这个是 Devtools 调试工具,Devtools 可以进行性能的调试

Flutter-热重载原理解析

Flutter-热重载原理解析

扫描增量文件

  1. 监听终端的输入,然后进行处理

Flutter-热重载原理解析

Flutter-热重载原理解析

这里可以看到输入 r 对应的处理,热更新主要执行的代码

Flutter-热重载原理解析

  1. 输入r 进行 hot reload 刷新展示 residentRunner.restart(fullRestart: false),restart 中继续执行下面的方法

Flutter-热重载原理解析

  1. 跟进 restart 方法里面的 _fullRestartHelper 方法和 _reloadSources 方法对资源文件进行重新加载

Flutter-热重载原理解析

  1. 记录 hotreload 的加载时间,然后更新增量文件

Flutter-热重载原理解析

  1. 跟进 _updateDevFS 更新增量文件,找到设备执行更新文件,再跟进去找到了update方法

Flutter-热重载原理解析

Flutter-热重载原理解析

  1. 获取 content 中的路径然后查找这个文件,然后终端进入这个目录,输入 strings app.dill.incremental.dill,假如有更改内容就可以输出更改文件的内容

Flutter-热重载原理解析

  1. 这就是根据路径找到的文件,app.dill.incremental.dill 就包含增量的文件内容

Flutter-热重载原理解析

传输增加文件给 Dart VM

  1. 传输增量文件 DevFSContent 给虚拟机,通过 _httpWriter.writer 异步走 RPC 协议写入到 DartVM中更新 await _httpWriter.write(dirtyEntries);

Flutter-热重载原理解析

可以查看这个_httpWriter里面有个属性 httpAddress 就是 Dart VM 的地址

Flutter-热重载原理解析

  1. 那虚拟机什么时候创建呢,其实打断点可以看到就是开始 run 的时候就创建了,创建里面就会有一系列的注册

Flutter-热重载原理解析

Flutter-热重载原理解析

热重载引擎的联调

因为之前我是下载了flutter 引擎代码的,然后编译成了对应的iOS工程,所以这里可以进行联调。

  1. 如何让 Flutter 工程中的iOS工程使用本地编译好的引擎,需要在 RunnerGenerated.xcconfig 里面配置引擎
//编译引擎的路径
FLUTTER_ENGINE=/Users/zhou/engine_download/src
//对应的模拟器debug版本
LOCAL_ENGINE=ios_debug_sim_unopt

配置完成后就开始运行Runner工程,我在flutter 引擎里面在点击方法里面添加了一个打印

Flutter-热重载原理解析

当我运行其他项目后,点击屏幕控制台输出了 点击了,这就说明了挂载成功了

Flutter-热重载原理解析

  1. 接下来联调 flutter tools ,需要在 Edit Configurations 里面 Program arguments 配置参数
run --local-engine-src-path /Users/zhou/engine_download/src --local-engine=ios_debug_sim_unopt

跑起来项目后,然后在 Xcode->Debug->Attach to process 里面附加工程,再打进入 /Users/zhou/engine_download/src/out/ios_debug_sim_unopt 打开iOS工程,找到这个方法,这个就是我们更新加载增量文件要调用的方法

Flutter-热重载原理解析

在刚才运行的Xcode工程里面添加断点

br set -n "IsolateGroupReloadContext::Reload"

Flutter-热重载原理解析

这样就形成了一个闭环,引擎代码、Flutter 代码,flutter tools ,然后需改Flutter代码,在 flutter tools 里面执行 r ,发现Xcode断点到了 IsolateGroupReloadContext 这个方法开始执行增加渲染,说明验证了之前的热重载的步骤。

总结

这样一步步的调试下来就能大概明白热重载的原理。Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。

另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。

可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。

参考文章:

www.jianshu.com/p/46b43554b…

zhuanlan.zhihu.com/p/89870807