Flutter进阶-热重载原理

221 阅读4分钟

热重载启动流程

我们在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函数中可以找到

@override
void printHelp({ @required bool details }) {
  globals.printStatus('Flutter run key commands.');
  commandHelp.r.print();
  if (supportsRestart) {
    commandHelp.R.print();
  }
  if (details) {
    printHelpDetails();
    commandHelp.hWithDetails.print();
  } else {
    commandHelp.hWithoutDetails.print();
  }
  if (_didAttach) {
    commandHelp.d.print();
  }
  commandHelp.c.print();
  commandHelp.q.print();
  globals.printStatus('');
  if (debuggingOptions.buildInfo.nullSafetyMode ==  NullSafetyMode.sound) {
    globals.printStatus('💪 Running with sound null safety 💪', emphasis: true);
  } else {
    globals.printStatus(
      'Running with unsound null safety',
      emphasis: true,
    );
    globals.printStatus(
      'For more information see https://dart.dev/null-safety/unsound-null-safety',
    );
  }
  globals.printStatus('');
  printDebuggerList();
  }

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(增量更新)

    for (final FlutterDevice device in flutterDevices) {
      results.incorporateResults(await device.updateDevFS(
        mainUri: entrypointFile.absolute.uri,
        target: target,
        bundle: assetBundle,
        firstBuildTime: firstBuildTime,
        bundleFirstUpload: isFirstUpload,
        bundleDirty: !isFirstUpload && rebuildBundle,
        fullRestart: fullRestart,
        projectRootPath: projectRootPath,
        pathToReload: getReloadPath(fullRestart: fullRestart, swap: _swap),
        invalidatedFiles: invalidationResult.uris,
        packageConfig: invalidationResult.packageConfig,
        dillOutputPath: dillOutputPath,
      ));
    }

然后在for循环内-> device.updateDevFS -> devFS.update 断点调试这里可以看到这里的编译二进制文件的一个地址

image.png

前往文件夹,打开该地址,查看文件

image.png

可以看出这个文件没有内容,我们在刚刚被挂载的项目my_flutter的测试demo中,修改一下main.dart的内容或者增加个注释也可

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

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

虚拟机

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

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

image.png

_httpWriter的httpAddress即是上文中提到的虚拟机的地址

image.png

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

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

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

指定flutter引擎

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

// ios配置文件指定引擎
FLUTTER_ENGINE=/Users/liukun/engine/src
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