1仙逻、_commonTerminalInputHandler
熱重載和熱重啟部分相同的驰吓。都是執(zhí)行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;
}
這段代碼實(shí)現(xiàn)了一個(gè) restart 方法系奉,接收幾個(gè)可選參數(shù):fullRestart檬贰,reason,silent 和 pause缺亮,返回一個(gè) Future<OperationResult> 對(duì)象翁涤。
該方法首先檢查所有的 flutterDevices 是否都已經(jīng)初始化完成,如果有任何一個(gè)設(shè)備的 devFS 為 null萌踱,則返回一個(gè) OperationResult 對(duì)象葵礼,表示設(shè)備尚未初始化完成。
接著并鸵,該方法會(huì)調(diào)用 _calculateTargetPlatform 方法來計(jì)算目標(biāo)平臺(tái)鸳粉,并啟動(dòng)一個(gè)計(jì)時(shí)器。
然后园担,該方法會(huì)調(diào)用 runSourceGenerators 方法來運(yùn)行源代碼生成器(如果需要的話)届谈。
如果 fullRestart 參數(shù)為 true枯夜,則調(diào)用 _fullRestartHelper 方法來執(zhí)行一個(gè)完全重啟操作,并打印重啟耗時(shí)艰山。否則湖雹,調(diào)用 _hotReloadHelper 方法來執(zhí)行熱重載操作,并打印操作結(jié)果和耗時(shí)曙搬。
最后摔吏,返回操作結(jié)果。
熱重啟和熱重載纵装,都會(huì)經(jīng)過重新生成源代碼的過程征讲。
_calculateTargetPlatform -》runSourceGenerators
_fullRestartHelper 執(zhí)行熱重啟,_hotReloadHelper 執(zhí)行熱重載橡娄。
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');
}
這段代碼實(shí)現(xiàn)了一個(gè) runSourceGenerators() 方法,它的作用是運(yùn)行Flutter應(yīng)用程序的源代碼生成器來生成本地化和Dart插件注冊(cè)表瀑踢。
該方法首先使用 Flutter 環(huán)境變量(_environment)初始化一個(gè) CompositeTarget扳还,該 CompositeTarget 包含了兩個(gè) Target,分別是 GenerateLocalizationsTarget() 和 DartPluginRegistrantTarget()橱夭。
接著氨距,它使用 globals.buildSystem.buildIncremental() 方法來增量構(gòu)建這個(gè) CompositeTarget,構(gòu)建環(huán)境為 _environment棘劣,上一次構(gòu)建的結(jié)果為 _lastBuild俏让。這個(gè)方法返回一個(gè) BuildResult 對(duì)象,其中包含了本次構(gòu)建的成功或失敗信息茬暇。
如果構(gòu)建失敗首昔,它會(huì)遍歷 _lastBuild.exceptions 中的所有異常,并將它們打印出來糙俗,包括異常信息和堆棧跟蹤(如果日志級(jí)別是 verbose 的話)勒奇。
最后,它會(huì)將日志信息打印到控制臺(tái)巧骚,并返回赊颠。注意,該方法不返回任何值劈彪,但它可能會(huì)改變一些類成員變量的值竣蹦,比如 _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;
}
這段代碼實(shí)現(xiàn)了一個(gè)異步函數(shù) buildIncremental痘括,用于增量構(gòu)建指定目標(biāo)(Target)的代碼,并在給定環(huán)境(Environment)下執(zhí)行構(gòu)建過程滔吠。它接受三個(gè)參數(shù):目標(biāo)纲菌、環(huán)境和先前的構(gòu)建結(jié)果(可選)抄淑。它返回一個(gè) Future<BuildResult>,其中包含構(gòu)建結(jié)果驰后。
函數(shù)的執(zhí)行流程如下:
- 首先,創(chuàng)建構(gòu)建目錄和輸出目錄矗愧,如果它們不存在的話灶芝。
- 然后,根據(jù)先前的構(gòu)建結(jié)果是否為 null唉韭,創(chuàng)建一個(gè)文件緩存對(duì)象(FileStore)或從緩存中獲取一個(gè)夜涕。這個(gè)緩存會(huì)記錄每個(gè)構(gòu)建步驟的輸入和輸出文件,并根據(jù)它們的時(shí)間戳來判斷哪些步驟需要重新構(gòu)建属愤。
- 接下來女器,將目標(biāo)對(duì)象轉(zhuǎn)換為一個(gè)依賴圖中的節(jié)點(diǎn)(Node)對(duì)象。
創(chuàng)建一個(gè) _BuildInstance 實(shí)例住诸,并將它需要的參數(shù)傳遞給它驾胆,包括環(huán)境、文件緩存贱呐、日志記錄器丧诺、文件系統(tǒng)和平臺(tái)等。 - 然后奄薇,調(diào)用 _BuildInstance.invokeTarget() 方法驳阎,傳遞目標(biāo)節(jié)點(diǎn),開始構(gòu)建過程馁蒂。如果構(gòu)建過程中有異常發(fā)生呵晚,異常會(huì)被捕獲并保存到 buildInstance.exceptionMeasurements 列表中。
- 最后沫屡,調(diào)用 fileCache.persistIncremental() 方法饵隙,將增量構(gòu)建所用到的緩存保存到磁盤上。
- 創(chuàng)建一個(gè) BuildResult 對(duì)象沮脖,其中包含構(gòu)建結(jié)果的成功或失敗狀態(tài)癞季、異常列表和性能指標(biāo),然后將該結(jié)果與緩存對(duì)象一起保存到 _incrementalFileStore 字典中倘潜,以備下次增量構(gòu)建時(shí)使用绷柒。
- 返回 BuildResult 對(duì)象作為異步操作的結(jié)果。
因此涮因,這段代碼實(shí)現(xiàn)了一個(gè)增量構(gòu)建過程废睦,其中緩存機(jī)制可以提高構(gòu)建性能,只有在必要時(shí)才會(huì)重新構(gòu)建文件养泡。這可以節(jié)省時(shí)間和資源嗜湃,特別是在大型項(xiàng)目中奈应。
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,
);
}
在這里發(fā)現(xiàn)一個(gè)目錄:
/private/var/folders/4j/z1n6phgx4d11klg60p7d3b240000gn/T/flutter_tools.COlwZv/flutter_tool.Kr5rSU/app.dill
編譯過程:
編譯同步過程:
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;
}
在這個(gè)方法添加上日志杖挣,比較hot reload 和hot restart 區(qū)別:
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
從上面調(diào)試過程中的,可以看出下面轉(zhuǎn)載的內(nèi)容沒有什么出入刚陡。
下面內(nèi)容從https://juejin.cn/post/7041728779038752799轉(zhuǎn)載:
熱重載的步驟:
- 工程改動(dòng):熱重載Server會(huì)逐一掃描工程中的文件惩妇,檢查是否有新增、刪除或者改動(dòng)筐乳,直到找到在上次編譯之后歌殃,發(fā)生變化的 Dart 代碼。
增量編譯:熱重載模塊會(huì)將發(fā)生變化的 Dart 代碼蝙云,通過編譯轉(zhuǎn)化為增量的 Dart Kernel 文件氓皱。 - 推送更新:熱重載Server將增量的 Dart Kernel 文件通過 RPC 協(xié)議,發(fā)送給正在手機(jī)上運(yùn)行的 Dart VM勃刨。
- 代碼合并:Dart VM 會(huì)將收到的增量 Dart Kernel 文件波材,與原有的 Dart Kernel 文件進(jìn)行合并,然后重新加載新的 Dart Kernel 文件身隐。
- Widget增量渲染:在確認(rèn) Dart VM 資源加載成功后各聘,F(xiàn)lutter 會(huì)將其 UI 線程重置,通知 flutter.framework 重建 Widget抡医。
不支持熱重載的場(chǎng)景
- main 方法里的更改
- initState 方法里的更改
- 代碼出現(xiàn)編譯錯(cuò)誤
- 全局變量和靜態(tài)屬性的更改
- Widget 狀態(tài)無法兼容
- 枚舉和泛類型更改
Flutter 的熱重載是基于 JIT 編譯模式的代碼增量同步躲因。由于 JIT 屬于動(dòng)態(tài)編譯,能夠?qū)?Dart 代碼編譯成生成中間代碼忌傻,讓 Dart VM 在運(yùn)行時(shí)解釋執(zhí)行大脉,因此可以通過動(dòng)態(tài)更新中間代碼實(shí)現(xiàn)增量同步。