美文网首页
2023-02-24 flutter热重载和热重启

2023-02-24 flutter热重载和热重启

作者: 我是小胡胡分胡 | 来源:发表于2023-02-23 17:58 被阅读0次

    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 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。

    相关文章

      网友评论

          本文标题:2023-02-24 flutter热重载和热重启

          本文链接:https://www.haomeiwen.com/subject/bhqtldtx.html