1、_commonTerminalInputHandler
热重载和热重启部分相同的。都是执行residentRunner.restart();
热重载:
final OperationResult result = await residentRunner.restart();
热重启:
final OperationResult result = await residentRunner.restart(fullRestart: true);
case 'r':
if (!residentRunner.canHotReload) {
return false;
}
final OperationResult result = await residentRunner.restart();
if (result.fatal) {
throwToolExit(result.message);
}
if (!result.isOk) {
_logger.printStatus('Try again after fixing the above error(s).', emphasis: true);
}
return true;
case 'R':
// If hot restart is not supported for all devices, ignore the command.
if (!residentRunner.supportsRestart || !residentRunner.hotMode) {
return false;
}
final OperationResult result = await residentRunner.restart(fullRestart: true);
if (result.fatal) {
throwToolExit(result.message);
}
if (!result.isOk) {
_logger.printStatus('Try again after fixing the above error(s).', emphasis: true);
}
return true;
2、residentRunner.restart();
Future<OperationResult> restart({
bool fullRestart = false,
String reason,
bool silent = false,
bool pause = false,
}) async {
if (flutterDevices.any((FlutterDevice device) => device.devFS == null)) {
return OperationResult(1, 'Device initialization has not completed.');
}
await _calculateTargetPlatform();
final Stopwatch timer = Stopwatch()..start();
// Run source generation if needed.
await runSourceGenerators();
if (fullRestart) {
final OperationResult result = await _fullRestartHelper(
targetPlatform: _targetPlatform,
sdkName: _sdkName,
emulator: _emulator,
reason: reason,
silent: silent,
);
if (!silent) {
globals.printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
}
unawaited(residentDevtoolsHandler.hotRestart(flutterDevices));
return result;
}
final OperationResult result = await _hotReloadHelper(
targetPlatform: _targetPlatform,
sdkName: _sdkName,
emulator: _emulator,
reason: reason,
pause: pause,
);
if (result.isOk) {
final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
if (!silent) {
globals.printStatus('${result.message} in $elapsed.');
}
}
return result;
}
这段代码实现了一个 restart 方法,接收几个可选参数:fullRestart,reason,silent 和 pause,返回一个 Future<OperationResult> 对象。
-
该方法首先检查所有的 flutterDevices 是否都已经初始化完成,如果有任何一个设备的 devFS 为 null,则返回一个 OperationResult 对象,表示设备尚未初始化完成。
-
接着,该方法会调用 _calculateTargetPlatform 方法来计算目标平台,并启动一个计时器。
-
然后,该方法会调用 runSourceGenerators 方法来运行源代码生成器(如果需要的话)。
-
如果 fullRestart 参数为 true,则调用 _fullRestartHelper 方法来执行一个完全重启操作,并打印重启耗时。否则,调用 _hotReloadHelper 方法来执行热重载操作,并打印操作结果和耗时。
最后,返回操作结果。
热重启和热重载,都会经过重新生成源代码的过程。
_calculateTargetPlatform -》runSourceGenerators
_fullRestartHelper 执行热重启,_hotReloadHelper 执行热重载。
3、runSourceGenerators
Future<void> runSourceGenerators() async {
_environment ??= Environment(
artifacts: globals.artifacts,
logger: globals.logger,
cacheDir: globals.cache.getRoot(),
engineVersion: globals.flutterVersion.engineRevision,
fileSystem: globals.fs,
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager,
platform: globals.platform,
projectDir: globals.fs.currentDirectory,
generateDartPluginRegistry: generateDartPluginRegistry,
defines: <String, String>{
// Needed for Dart plugin registry generation.
kTargetFile: mainPath,
},
);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[
const GenerateLocalizationsTarget(),
const DartPluginRegistrantTarget(),
]);
_lastBuild = await globals.buildSystem.buildIncremental(
compositeTarget,
_environment,
_lastBuild,
);
if (!_lastBuild.success) {
for (final ExceptionMeasurement exceptionMeasurement in _lastBuild.exceptions.values) {
globals.printError(
exceptionMeasurement.exception.toString(),
stackTrace: globals.logger.isVerbose
? exceptionMeasurement.stackTrace
: null,
);
}
}
globals.printTrace('complete');
}
这段代码实现了一个 runSourceGenerators() 方法,它的作用是运行Flutter应用程序的源代码生成器来生成本地化和Dart插件注册表。
-
该方法首先使用 Flutter 环境变量(_environment)初始化一个 CompositeTarget,该 CompositeTarget 包含了两个 Target,分别是 GenerateLocalizationsTarget() 和 DartPluginRegistrantTarget()。
-
接着,它使用 globals.buildSystem.buildIncremental() 方法来增量构建这个 CompositeTarget,构建环境为 _environment,上一次构建的结果为 _lastBuild。这个方法返回一个 BuildResult 对象,其中包含了本次构建的成功或失败信息。
-
如果构建失败,它会遍历 _lastBuild.exceptions 中的所有异常,并将它们打印出来,包括异常信息和堆栈跟踪(如果日志级别是 verbose 的话)。
-
最后,它会将日志信息打印到控制台,并返回。注意,该方法不返回任何值,但它可能会改变一些类成员变量的值,比如 _environment 和 _lastBuild。
4、 buildIncremental
Future<BuildResult> buildIncremental(
Target target,
Environment environment,
BuildResult? previousBuild,
) async {
environment.buildDir.createSync(recursive: true);
environment.outputDir.createSync(recursive: true);
FileStore? fileCache;
if (previousBuild == null || _incrementalFileStore[previousBuild] == null) {
final File cacheFile = environment.buildDir.childFile(FileStore.kFileCache);
fileCache = FileStore(
cacheFile: cacheFile,
logger: _logger,
strategy: FileStoreStrategy.timestamp,
)..initialize();
} else {
fileCache = _incrementalFileStore[previousBuild];
}
final Node node = target._toNode(environment);
final _BuildInstance buildInstance = _BuildInstance(
environment: environment,
fileCache: fileCache!,
buildSystemConfig: const BuildSystemConfig(),
logger: _logger,
fileSystem: _fileSystem,
platform: _platform,
);
bool passed = true;
try {
passed = await buildInstance.invokeTarget(node);
} finally {
fileCache.persistIncremental();
}
final BuildResult result = BuildResult(
success: passed,
exceptions: buildInstance.exceptionMeasurements,
performance: buildInstance.stepTimings,
);
_incrementalFileStore[result] = fileCache;
return result;
}
这段代码实现了一个异步函数 buildIncremental,用于增量构建指定目标(Target)的代码,并在给定环境(Environment)下执行构建过程。它接受三个参数:目标、环境和先前的构建结果(可选)。它返回一个 Future<BuildResult>,其中包含构建结果。
函数的执行流程如下:
- 首先,创建构建目录和输出目录,如果它们不存在的话。
- 然后,根据先前的构建结果是否为 null,创建一个文件缓存对象(FileStore)或从缓存中获取一个。这个缓存会记录每个构建步骤的输入和输出文件,并根据它们的时间戳来判断哪些步骤需要重新构建。
- 接下来,将目标对象转换为一个依赖图中的节点(Node)对象。
创建一个 _BuildInstance 实例,并将它需要的参数传递给它,包括环境、文件缓存、日志记录器、文件系统和平台等。 - 然后,调用 _BuildInstance.invokeTarget() 方法,传递目标节点,开始构建过程。如果构建过程中有异常发生,异常会被捕获并保存到 buildInstance.exceptionMeasurements 列表中。
- 最后,调用 fileCache.persistIncremental() 方法,将增量构建所用到的缓存保存到磁盘上。
- 创建一个 BuildResult 对象,其中包含构建结果的成功或失败状态、异常列表和性能指标,然后将该结果与缓存对象一起保存到 _incrementalFileStore 字典中,以备下次增量构建时使用。
- 返回 BuildResult 对象作为异步操作的结果。
因此,这段代码实现了一个增量构建过程,其中缓存机制可以提高构建性能,只有在必要时才会重新构建文件。这可以节省时间和资源,特别是在大型项目中。
5、 _fullRestartHelper
Future<OperationResult> _fullRestartHelper({
String targetPlatform,
String sdkName,
bool emulator,
String reason,
bool silent,
}) async {
if (!supportsRestart) {
return OperationResult(1, 'hotRestart not supported');
}
Status status;
if (!silent) {
status = globals.logger.startProgress(
'Performing hot restart...',
progressId: 'hot.restart',
);
}
OperationResult result;
String restartEvent;
try {
final Stopwatch restartTimer = _stopwatchFactory.createStopwatch('fullRestartHelper')..start();
if (!(await hotRunnerConfig.setupHotRestart())) {
return OperationResult(1, 'setupHotRestart failed');
}
result = await _restartFromSources(reason: reason);
restartTimer.stop();
if (!result.isOk) {
restartEvent = 'restart-failed';
} else {
HotEvent('restart',
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: true,
reason: reason,
fastReassemble: false,
overallTimeInMs: restartTimer.elapsed.inMilliseconds,
syncedBytes: result.updateFSReport?.syncedBytes,
invalidatedSourcesCount: result.updateFSReport?.invalidatedSourcesCount,
transferTimeInMs: result.updateFSReport?.transferDuration?.inMilliseconds,
compileTimeInMs: result.updateFSReport?.compileDuration?.inMilliseconds,
findInvalidatedTimeInMs: result.updateFSReport?.findInvalidatedDuration?.inMilliseconds,
scannedSourcesCount: result.updateFSReport?.scannedSourcesCount,
).send();
}
} on vm_service.SentinelException catch (err, st) {
restartEvent = 'exception';
return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true);
} on vm_service.RPCError catch (err, st) {
restartEvent = 'exception';
return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true);
} finally {
// The `restartEvent` variable will be null if restart succeeded. We will
// only handle the case when it failed here.
if (restartEvent != null) {
HotEvent(restartEvent,
targetPlatform: targetPlatform,
sdkName: sdkName,
emulator: emulator,
fullRestart: true,
reason: reason,
fastReassemble: false,
).send();
}
status?.cancel();
}
return result;
}
6、 await _restartFromSources(reason: reason);
Future<OperationResult> _restartFromSources({
String reason,
}) async {
final Stopwatch restartTimer = Stopwatch()..start();
UpdateFSReport updatedDevFS;
try {
updatedDevFS = await _updateDevFS(fullRestart: true);
} finally {
hotRunnerConfig.updateDevFSComplete();
}
if (!updatedDevFS.success) {
for (final FlutterDevice device in flutterDevices) {
if (device.generator != null) {
await device.generator.reject();
}
}
return OperationResult(1, 'DevFS synchronization failed');
}
_resetDirtyAssets();
for (final FlutterDevice device in flutterDevices) {
// VM must have accepted the kernel binary, there will be no reload
// report, so we let incremental compiler know that source code was accepted.
if (device.generator != null) {
device.generator.accept();
}
}
// Check if the isolate is paused and resume it.
final List<Future<void>> operations = <Future<void>>[];
for (final FlutterDevice device in flutterDevices) {
final Set<String> uiIsolatesIds = <String>{};
final List<FlutterView> views = await device.vmService.getFlutterViews();
for (final FlutterView view in views) {
if (view.uiIsolate == null) {
continue;
}
uiIsolatesIds.add(view.uiIsolate.id);
// Reload the isolate.
final Future<vm_service.Isolate> reloadIsolate = device.vmService
.getIsolateOrNull(view.uiIsolate.id);
operations.add(reloadIsolate.then((vm_service.Isolate isolate) async {
if ((isolate != null) && isPauseEvent(isolate.pauseEvent.kind)) {
// The embedder requires that the isolate is unpaused, because the
// runInView method requires interaction with dart engine APIs that
// are not thread-safe, and thus must be run on the same thread that
// would be blocked by the pause. Simply un-pausing is not sufficient,
// because this does not prevent the isolate from immediately hitting
// a breakpoint (for example if the breakpoint was placed in a loop
// or in a frequently called method) or an exception. Instead, all
// breakpoints are first disabled and exception pause mode set to
// None, and then the isolate resumed.
// These settings to not need restoring as Hot Restart results in
// new isolates, which will be configured by the editor as they are
// started.
final List<Future<void>> breakpointAndExceptionRemoval = <Future<void>>[
device.vmService.service.setIsolatePauseMode(isolate.id,
exceptionPauseMode: vm_service.ExceptionPauseMode.kNone),
for (final vm_service.Breakpoint breakpoint in isolate.breakpoints)
device.vmService.service.removeBreakpoint(isolate.id, breakpoint.id)
];
await Future.wait(breakpointAndExceptionRemoval);
await device.vmService.service.resume(view.uiIsolate.id);
}
}));
}
// The engine handles killing and recreating isolates that it has spawned
// ("uiIsolates"). The isolates that were spawned from these uiIsolates
// will not be restarted, and so they must be manually killed.
final vm_service.VM vm = await device.vmService.service.getVM();
for (final vm_service.IsolateRef isolateRef in vm.isolates) {
if (uiIsolatesIds.contains(isolateRef.id)) {
continue;
}
operations.add(device.vmService.service.kill(isolateRef.id)
.catchError((dynamic error, StackTrace stackTrace) {
// Do nothing on a SentinelException since it means the isolate
// has already been killed.
// Error code 105 indicates the isolate is not yet runnable, and might
// be triggered if the tool is attempting to kill the asset parsing
// isolate before it has finished starting up.
}, test: (dynamic error) => error is vm_service.SentinelException
|| (error is vm_service.RPCError && error.code == 105)));
}
}
await Future.wait(operations);
await _launchFromDevFS();
restartTimer.stop();
globals.printTrace('Hot restart performed in ${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
_addBenchmarkData('hotRestartMillisecondsToFrame',
restartTimer.elapsed.inMilliseconds);
// Send timing analytics.
globals.flutterUsage.sendTiming('hot', 'restart', restartTimer.elapsed);
// Toggle the main dill name after successfully uploading.
_swap =! _swap;
return OperationResult(
OperationResult.ok.code,
OperationResult.ok.message,
updateFSReport: updatedDevFS,
);
}
在这里发现一个目录:
/private/var/folders/4j/z1n6phgx4d11klg60p7d3b240000gn/T/flutter_tools.COlwZv/flutter_tool.Kr5rSU/app.dill
编译过程:
image.png
编译同步过程:
image.png
VmService call方法:
Future<T> _call<T>(String method, [Map args = const {}]) async {
print('--->method:$method');
final request = _OutstandingRequest(method);
_outstandingRequests[request.id] = request;
Map m = {
'jsonrpc': '2.0',
'id': request.id,
'method': method,
'params': args,
};
String message = jsonEncode(m);
_onSend.add(message);
_writeMessage(message);
return await request.future as T;
}
在这个方法添加上日志,比较hot reload 和hot restart 区别:
-
flutter attach
--->method:registerService
--->method:getVersion
--->method:registerService
--->method:registerService
--->method:registerService
--->method:registerService
--->method:registerService
--->method:registerService
--->method:streamListen
--->method:getVersion
--->method:_flutter.listViews
--->method:getVM
--->method:_createDevFS
Syncing files to device iPhone 14 Pro...
--->method:streamListen
--->method:_flutter.listViews
--->method:getIsolate
--->method:streamCancel
--->method:_flutter.listViews
--->method:ext.flutter.activeDevToolsServerAddress
--->method:_flutter.listViews
--->method:ext.flutter.connectedVmServiceUri -
热重载:
--->method:_flutter.listViews
--->method:getIsolate
--->method:ext.flutter.reassemble -
热重启:
--->method:_flutter.listViews
--->method:getIsolate
--->method:getVM
--->method:_flutter.listViews
--->method:streamListen
--->method:_flutter.runInView
Restarted application in 1,419ms.
--->method:streamListen
--->method:_flutter.listViews
--->method:getIsolate
--->method:streamCancel
--->method:_flutter.listViews
--->method:_flutter.listViews
--->method:ext.flutter.activeDevToolsServerAddress
--->method:ext.flutter.connectedVmServiceUri
从上面调试过程中的,可以看出下面转载的内容没有什么出入。
下面内容从https://juejin.cn/post/7041728779038752799转载:
热重载的步骤:
- 工程改动:热重载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 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。
网友评论