热重载启动流程

我们在Dart代码上修改了一下内容,马上就能在客户端上看到效果。这种增量更新的机制是怎么样的?今天可以来试着研究一下。前面介绍我们知道flutter的源码在这里
image.png

项目配置

打开flutter_tools指定需要挂载的调试代码
image.png
点击”+“
image.png
配置文件

  1. Dart file: 源码flutter_tools中flutter_tools.dart的路径
  2. Program arguments: run(运行)
  3. Working directory:搭载的测试项目(需要调试的flutter项目)

image.png
如果此时下面提示Dart SDK is not Configured in Flutter解决方法也比较简单,打开AS的Preferences然后Apply之后即可。
image.png
设置了之后就可以正常运行成功。
image.png

断点调试

flutter_tools.dart文件中的main函数出下个断点,来简单的跟一下设备启动之前的流程,main函数中的参数是run也就是上面配置文件中配置的run
image.png
然后到了runner.run ->generateCommands -> runCommand�() -> ..registerSignalHandlers()注册 -> ..setupTerminal()启动终端 - >residentRunner.printHelp
这里的..setupTerminal()运行之后,控制台终端命令就出来了, 这些输入都是在printHelp函数中可以找到

  1. @override
  2. void printHelp({ @required bool details }) {
  3. globals.printStatus('Flutter run key commands.');
  4. commandHelp.r.print();
  5. if (supportsRestart) {
  6. commandHelp.R.print();
  7. }
  8. if (details) {
  9. printHelpDetails();
  10. commandHelp.hWithDetails.print();
  11. } else {
  12. commandHelp.hWithoutDetails.print();
  13. }
  14. if (_didAttach) {
  15. commandHelp.d.print();
  16. }
  17. commandHelp.c.print();
  18. commandHelp.q.print();
  19. globals.printStatus('');
  20. if (debuggingOptions.buildInfo.nullSafetyMode == NullSafetyMode.sound) {
  21. globals.printStatus('💪 Running with sound null safety 💪', emphasis: true);
  22. } else {
  23. globals.printStatus(
  24. 'Running with unsound null safety',
  25. emphasis: true,
  26. );
  27. globals.printStatus(
  28. 'For more information see https://dart.dev/null-safety/unsound-null-safety',
  29. );
  30. }
  31. globals.printStatus('');
  32. printDebuggerList();
  33. }

image.png
这里的第一个地址是虚拟机的地址,第二个地址是debug的工具。

Hot reload

r Hot reload.r 即为热重载,那么我们输入r的时候,具体调用了什么,接着往下看:
setupTerminal() -> _terminal.keystrokes.listen(processTerminalInput) -> processTerminalInput -> _commonTerminalInputHandler(command) -> residentRunner.restart(fullRestart: false)来到了run_hot.dart中的这个restart的方法 -> _hotReloadHelper
image.png
-> _reloadSources(这里就是加载改动的dart代码) -> device.vmService.getFlutterViews(获取vm虚拟机) -> _updateDevFS(增量更新)

  1. for (final FlutterDevice device in flutterDevices) {
  2. results.incorporateResults(await device.updateDevFS(
  3. mainUri: entrypointFile.absolute.uri,
  4. target: target,
  5. bundle: assetBundle,
  6. firstBuildTime: firstBuildTime,
  7. bundleFirstUpload: isFirstUpload,
  8. bundleDirty: !isFirstUpload && rebuildBundle,
  9. fullRestart: fullRestart,
  10. projectRootPath: projectRootPath,
  11. pathToReload: getReloadPath(fullRestart: fullRestart, swap: _swap),
  12. invalidatedFiles: invalidationResult.uris,
  13. packageConfig: invalidationResult.packageConfig,
  14. dillOutputPath: dillOutputPath,
  15. ));
  16. }

然后在for循环内-> device.updateDevFS -> devFS.update
断点调试这里可以看到这里的编译二进制文件的一个地址
image.png
前往文件夹,打开该地址,查看文件
image.png
可以看出这个文件没有内容,我们在刚刚被挂载的项目my_flutter的测试demo中,修改一下main.dart的内容或者增加个注释也可

  1. title: 'Demo11111111', // 测试由’Demo‘修改为’Demo11111111‘

然后回到flutter_tools.dart中,在控制台中输入r又来到了刚才的断点处
image.png
找到改地址,打开看下文件的变化
image.png
这里就可以明显的看到app.dill.incremental.dill这个文件就是增量的二进制文件的内容。由此,可以得出在dart中增量渲染是以文件为单位的,即使只增加了注释也是一样的效果。

虚拟机

拿到这个值之后怎么传给虚拟机呢?dirtyEntries的value的值就是刚才增量文件的地址

  1. await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri, _httpWriter);

image.png
_httpWriter的httpAddress即是上文中提到的虚拟机的地址
image.png
所以这行代码就是从dar本地给发送到了虚拟机。那么什么时候创建的虚拟机呢,来到vmservice.dart文件中的setUpVmService方法

  1. try {
  2. await Future.wait(registrationRequests);
  3. } on vm_service.RPCError catch (e) {
  4. throwToolExit('Failed to register service methods on attached VM Service: $e');
  5. }

这里虚拟机使用RPC与flutter.framework建立联系,也就是和flutter引擎交互。

指定flutter引擎

在测试的代码里面指定调试的引擎,这样xx.dart ->flutter.tools -> 虚拟机 -> framework引擎就形成了一个闭关。可以随时检测调试,指定引擎的方式,找到demo的iOS工程端配置下

  1. // ios配置文件指定引擎
  2. FLUTTER_ENGINE=/Users/liukun/engine/src
  3. LOCAL_ENGINE=ios_debug_sim_unopt

回到flutter.tools的配置项,指定Program arguments更改为
run --local-engine-src-path /Users/liukun/engine/src --local-engine=ios_debug_sim_unopt

  1. 运行flutter.tools
  2. 打开Xcode iOS Runner,Debug -> Attach to Process

image.png
�点击Pause program execution能断点成功就表明已经连接上了,接着来验证一下完整的流程

完整链路验证

  1. 1.修改demo中的dart文件
  2. 回到flutter_tools中断点在如下位置

image.png
3.回到Xcode中,下一个断点,该IsolateGroupReloadContext::Reload方法是引擎收到dart虚拟机发送的context需要更新的地方。
image.png
4.在控制台输入r之后过了上面的2个断点之后就直接来到了3的断点
image.png
归纳总结之后,整个完整的链路大概是下面这种情况:

热重载.png