热重载启动流程
我们在Dart代码上修改了一下内容,马上就能在客户端上看到效果。这种增量更新的机制是怎么样的?今天可以来试着研究一下。前面介绍我们知道flutter的源码在这里
项目配置
打开flutter_tools
指定需要挂载的调试代码
点击”+“
配置文件
- Dart file: 源码flutter_tools中
flutter_tools.dart
的路径 - Program arguments: run(运行)
- Working directory:搭载的测试项目(需要调试的flutter项目)
如果此时下面提示Dart SDK is not Configured in Flutter
解决方法也比较简单,打开AS的Preferences
然后Apply
之后即可。
设置了之后就可以正常运行成功。
断点调试
在flutter_tools.dart
文件中的main
函数出下个断点,来简单的跟一下设备启动之前的流程,main
函数中的参数是run
也就是上面配置文件中配置的run
然后到了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();
}
这里的第一个地址是虚拟机的地址,第二个地址是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
-> _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
断点调试这里可以看到这里的编译二进制文件的一个地址
前往文件夹,打开该地址,查看文件
可以看出这个文件没有内容,我们在刚刚被挂载的项目my_flutter
的测试demo中,修改一下main.dart的内容或者增加个注释也可
title: 'Demo11111111', // 测试由’Demo‘修改为’Demo11111111‘
然后回到flutter_tools.dart
中,在控制台中输入r
又来到了刚才的断点处
找到改地址,打开看下文件的变化
这里就可以明显的看到app.dill.incremental.dill
这个文件就是增量的二进制文件的内容。由此,可以得出在dart中增量渲染是以文件为单位的,即使只增加了注释也是一样的效果。
虚拟机
拿到这个值之后怎么传给虚拟机呢?dirtyEntries的value的值就是刚才增量文件的地址
await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri, _httpWriter);
_httpWriter的httpAddress即是上文中提到的虚拟机的地址
所以这行代码就是从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
- 运行
flutter.tools
- 打开Xcode iOS Runner,
Debug
->Attach to Process
�点击Pause program execution
能断点成功就表明已经连接上了,接着来验证一下完整的流程
完整链路验证
- 1.修改demo中的dart文件
- 回到flutter_tools中断点在如下位置
3.回到Xcode中,下一个断点,该IsolateGroupReloadContext::Reload
方法是引擎收到dart虚拟机发送的context需要更新的地方。
4.在控制台输入r
之后过了上面的2个断点之后就直接来到了3的断点
归纳总结之后,整个完整的链路大概是下面这种情况: