5.Flutter热更新原理--简单跟踪

1.关于Dart中的运行方式

  • JIT:Just In Time . 动态解释,一边翻译一边执行,也称为即时编译,如JavaScript,Python等,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但是运行速度和性能则会受到影响,Flutter中的热重载正是基于此特性

  • AOT: Ahead of Time. 静态编译,是指程序在执行前全部被翻译为机器码,提前编译,如 C ,C++ ,OC等,发布时期使用AOT,就不需要像RN那样在跨平台JavaScript代码和原生Android、iOS代码间建立低效的方法调用映射关系。

程序的运行方式和具体的语言没有强制关系,比如python,既可以是JIT 也可以是AOT,Dart是少数同时支持JIT和AOT的语言之一。

Dart在开发过程中使用JIT,每次更改都不需要在编译成字节码,节省了大量时间,在部署中使用AOT生成高效的ARM代码保证高效的性能,所以说Dart具有运行速度快,执行性能好的特点。

2.热重载 Flutter 应用:

Flutter 的热重载功能可帮助您在无需重新启动应用程序的情况下快速、轻松地测试、构建用户界面、添加功能以及修复错误。通过将更新的源代码文件注入到正在运行的 Dart 虚拟机(VM) 来实现热重载。在虚拟机使用新的字段和函数更新类之后, Flutter 框架会自动重新构建 widget 树,以便您可以快速查看更改的效果。

  1. 在支持 Flutter 编辑器 或终端窗口运行应用程序,物理机或虚拟器都可以。 Flutter 应用程序只有在调试模式下才能被热重载。

  2. 修改项目中的一个Dart文件。大多数类型的代码更改可以热重载;一些需要重新启动应用程序的更改列表,请参阅 特别情况。

  3. 如果您在支持 Flutter IDE 工具的 IDE /编辑器中工作,请选择 Save All (cmd-s/ctrl-s),或单击工具栏上的 Hot Reload 按钮。

    如果您正在使用命令行 flutter run 运行应用程序,请在终端窗口输入 r

3.Flutter中热重载的执行步骤

1.在下载的flutterSDK中找到flutter_tools所在的目录使用Android Studio打开
hot_reload_1.png

2.点击configuration配置flutter_tool运行的环境,其中 3配置的是运行时传递过去的参数,flutter run或者flutter doctor类型的命令,4中设置的是你要热更新执行的项目,

由于同时在电脑上链接了多个设备,还要配置运行的设备 修改3中的参数 run -d CB6B23E5-1DCD-46E0-9B08-EAEC7CEAFE72,后面是模拟器的标识。开始运行

hot_reload_2.png

3.配置好之后点击开始运行
hot_reload_3.png

4.接下来 我们开始按照命令执行热更新,使用Debug 方式执行,首先会来到main函数执行入口,args中的参数就是我们在configuration中设置的参数,
hot_reload_4.png

5.而在main函数中,主要是一个异步的操作,执行runner.run方法,其中的参数是对各种命令的解析操作

Future main(List args) async {
 ……………………
   //主要查看run方法
  await runner.run(args, () => [
    AnalyzeCommand(
      verboseHelp: verboseHelp,
      fileSystem: globals.fs,
      platform: globals.platform,
      processManager: globals.processManager,
      logger: globals.logger,
      terminal: globals.terminal,
      artifacts: globals.artifacts,
    ),
    AssembleCommand(),
    …………………………

6.继续进行跟踪到runner.run目录中,调用runner.dart文件中 await runner.run(args);方法,继续开始执行发现到flutter_command_runner.dart

@override
  Future run(Iterable args) {
    // Have an invocation of 'build' print out it's sub-commands.
    // TODO(ianh): Move this to the Build command itself somehow.
    if (args.length == 1 && args.first == 'build') {
      args = ['build', '-h'];
    }
    //最终执行到run,
    return super.run(args);
  }

//父类中执行runCommand,此时的args也是传入的参数的值
Future run(Iterable args) =>
      Future.sync(() => runCommand(parse(args)));

7.在run.dart中执行 runCommand方法,获取当前设备的具体信息

hot_reload_5.png

8.接下来就是执行runCommand中下面的方法,

final Completer appStartedTimeRecorder = Completer.sync();
    // This callback can't throw.
    unawaited(appStartedTimeRecorder.future.then(
      (_) {
        appStartedTime = globals.systemClock.now();
        if (stayResident) {
          //更新控制台 添加监听
          TerminalHandler(runner)
            ..setupTerminal()
            ..registerSignalHandlers();
        }
      }
    ));

9.跳过中间接下来的几个步骤,在mac.dart文件调用buildXcodeProject执行xcrun等一系列的步骤,执行以下命令和并开始计时

hot_reload_6.png

在上面步骤8中 设置setUpTerminal,对终端输入的命令字进行解析

void setupTerminal() {
    if (!globals.logger.quiet) {
      globals.printStatus('');
      residentRunner.printHelp(details: false);
    }
    globals.terminal.singleCharMode = true;
    subscription = globals.terminal.keystrokes.listen(processTerminalInput);
  }
//查看监听方法 processTerminalInput,其中在_commonTerminalInputHandler方法中对输入的命令做了处理 
Future _commonTerminalInputHandler(String character) async {
    globals.printStatus(''); // the key the user tapped might be on this line
    switch(character) {
      case 'a':
        if (residentRunner.supportsServiceProtocol) {
          await residentRunner.debugToggleProfileWidgetBuilds();
          return true;
        }
        return false;
      ……………………………………………………
        //热更新主要执行的代码
      case 'r':
        if (!residentRunner.canHotReload) {
          return false;
        }
        final OperationResult result = await residentRunner.restart(fullRestart: false);
        if (result.fatal) {
          throwToolExit(result.message);
        }
        if (!result.isOk) {
          globals.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.canHotRestart || !residentRunner.hotMode) {
          return false;
        }
        final OperationResult result = await residentRunner.restart(fullRestart: true);
        if (result.fatal) {
          throwToolExit(result.message);
        }
        if (!result.isOk) {
          globals.printStatus('Try again after fixing the above error(s).', emphasis: true);
        }
        return true;
      ……………………………………………………
    }
    return false;
  }

10.输入r 进行hot reload刷新展示 residentRunner.restart(fullRestart: false); restart中继续执行下面的方法

@override
Future restart({
  bool fullRestart = false,
  String reason,
  bool benchmarkMode = false,
  bool silent = false,
  bool pause = false,
}) async {
  …………………………………………………………
  await runSourceGenerators();

  if (fullRestart) {
    //重点在这里, _fullRestartHelper,_reloadSources方法对资源文件进行重新加载
    final OperationResult result = await _fullRestartHelper(
      targetPlatform: targetPlatform,
      sdkName: sdkName,
      emulator: emulator,
      reason: reason,
      benchmarkMode: benchmarkMode,
      silent: silent,
    );
    ……………………………………………………
  }
  return result;
}

//更新加载资源
Future _reloadSources({
    String targetPlatform,
    String sdkName,
    bool emulator,
    bool pause = false,
    String reason,
    void Function(String message) onSlow,
  }) async {
    for (final FlutterDevice device in flutterDevices) {
      final List views = await device.vmService.getFlutterViews();
      for (final FlutterView view in views) {
        if (view.uiIsolate == null) {
          return OperationResult(2, 'Application isolate not found', fatal: true);
        }
      }
    }

    //记录hotreload的加载时间
    final Stopwatch reloadTimer = Stopwatch()..start();
    final Stopwatch devFSTimer = Stopwatch()..start();
    //更新文件--增量更新
    final UpdateFSReport updatedDevFS = await _updateDevFS();
    // Record time it took to synchronize to DevFS.
    bool shouldReportReloadTime = true;
    ……………………………………………………………………
  }

11.更新文件操作

Future _updateDevFS({ bool fullRestart = false }) async {
    ……………………………………………………………………………………
    final UpdateFSReport results = UpdateFSReport(success: true);
    //遍历更新文件
    for (final FlutterDevice device in flutterDevices) {
      //updateDevFS中更新对应文档
      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,
      ));
    }
    return results;
  }

12.在对updateDevFS进行跟踪,最后更新文件的实现

Future update({
    @required Uri mainUri,
    @required ResidentCompiler generator,
    @required bool trackWidgetCreation,
    @required String pathToReload,
    @required List invalidatedFiles,
    @required PackageConfig packageConfig,
    String target,
    AssetBundle bundle,
    DateTime firstBuildTime,
    bool bundleFirstUpload = false,
    String dillOutputPath,
    bool fullRestart = false,
    String projectRootPath,
    bool skipAssets = false,
  }) async {
    ………………………………………………………………………………………………………………………………
    if (!bundleFirstUpload) {
      final String compiledBinary = compilerOutput?.outputFilename;
      if (compiledBinary != null && compiledBinary.isNotEmpty) {
        final Uri entryUri = _fileSystem.path.toUri(projectRootPath != null
          ? _fileSystem.path.relative(pathToReload, from: projectRootPath)
          : pathToReload,
        );
        //获取content中的路径在文件中查找到这个文件,
        //终端中使用 strings app.dill.incremental.dill 文件中包含的就是增量更新的额内容
        final DevFSFileContent content = DevFSFileContent(_fileSystem.file(compiledBinary));
        syncedBytes += content.size;
        //读取到增量代码 添加到服务端
        dirtyEntries[entryUri] = content;
      }
    }
    _logger.printTrace('Updating files');
    if (dirtyEntries.isNotEmpty) {
      try {
        //添加到增量更新里面 通过网络注入到 DartVM中
        await _httpWriter.write(dirtyEntries);
      } on SocketException catch (socketException, stackTrace) {
        _logger.printTrace('DevFS sync failed. Lost connection to device: $socketException');
        throw DevFSException('Lost connection to device.', socketException, stackTrace);
      } on Exception catch (exception, stackTrace) {
        _logger.printError('Could not update files on device: $exception');
        throw DevFSException('Sync failed', exception, stackTrace);
      }
    }
    _logger.printTrace('DevFS: Sync finished');
    return UpdateFSReport(success: true, syncedBytes: syncedBytes,
         invalidatedSourcesCount: invalidatedFiles.length);
  }

13.接下来 我们更新一次看下对应的变化
hot_reload_8.gif

根据上面的路径找到对应的文件查看文件内容,strings /private/var/folders/r2/39t1cgw14x3gr81yktts4b940000gn/T/flutter_tools.qvz3CI/flutter_tool.Ydyzh3/app.dill.incremental.dill增量更新的位置已经发生了变化,添加到增量更新里面 通过_httpWriter.write注入到 DartVM中更新 await _httpWriter.write(dirtyEntries);

hot_reload_10.gif

你可能感兴趣的:(5.Flutter热更新原理--简单跟踪)