Flutter热重载原理探索调试

做过Flutter的同学都知道,Flutter支持 AOT(Ahead of time)和 JIT(Just in time) 两种编译模式。

AOT模式是运行前预先编译好的,运行过程中不需要进行分析,编译,此模式的运行速度是最快的。

JIT模式在运行过程中支持热重载Hot Reload,Flutter执行Hot Reload是一个增量的过程,由系统对本次的代码和上次的代码进行比对,找出差异性文件,然后将文件上传到DartVM中进行更新。

Flutter同时采用两种方案,在开发阶段采用JIT模式开发,在release阶段采用AOT模式,将代码打包成二进制进行发布。

Flutter支持JIT,那么理论上来说是可以支持热更新的,通过服务器来下发代码。但是这样会引起性能消耗,还有苹果禁止使用热更新,Flutter并没有采用热更新技术。

通过这篇文章,我们一起来探索一下Flutter热重载的实现。

1.热重载挂载

打开Flutter SDK目录下的packages/flutter_tools,将该工程拖至Android studio打开。
请添加图片描述
然后将工程挂载到需要调试的项目中
请添加图片描述
另外,如果没有指定flutter sdk,请指定sdk路径。
请添加图片描述
至此,挂载完成,接下来我们开始断点调试。

2.断点调试

程序入口在flutter_tools.dart的main函数

开始我们第一个断点
请添加图片描述
main里面就一个executable.main函数,点进去,我们可以看到main函数的代码,可以看到,main函数前面在做一些指令分析以及参数分析,这里不做过多介绍,重点是看下面的异步方法runner.run,不要再点进去断点调试了,因为这个方法都是回调,在generateCommands方法中已经调用过很多关键的方法,这里需要耐心的动态断点调试跟静态分析,通过调试与分析,我们来看一下RunCommand方法。点击进去。
请添加图片描述
还是好多异步回调,然后还是各种继承,有点闹心,继续跟进吧,找HotRunner,找到createRunner方法下return了一个HotRunner类,下个断点,看他的执行情况。
请添加图片描述
通过代码的分析,我们找到核心的方法_reloadSources,其中有一个关键的执行步骤_updateDevFS

Future<OperationResult> _reloadSources({
    String targetPlatform,
    String sdkName,
    bool emulator,
    bool pause = false,
    String reason,
    void Function(String message) onSlow,
  }) async {
    final Map<FlutterDevice, List<FlutterView>> viewCache = <FlutterDevice, List<FlutterView>>{};
    for (final FlutterDevice device in flutterDevices) {
      final List<FlutterView> views = await device.vmService.getFlutterViews();
      viewCache[device] = views;
      for (final FlutterView view in views) {
        if (view.uiIsolate == null) {
          return OperationResult(2, 'Application isolate not found', fatal: true);
        }
      }
    }

    final Stopwatch reloadTimer = _stopwatchFactory.createStopwatch('reloadSources:reload')..start();
    if (!(await hotRunnerConfig.setupHotReload())) {
      return OperationResult(1, 'setupHotReload failed');
    }
    final Stopwatch devFSTimer = Stopwatch()..start();
    UpdateFSReport updatedDevFS;
    try {
      updatedDevFS= await _updateDevFS();
    } finally {
      hotRunnerConfig.updateDevFSComplete();
    }
    // Record time it took to synchronize to DevFS.
    bool shouldReportReloadTime = true;
    _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds);
    if (!updatedDevFS.success) {
      return OperationResult(1, 'DevFS synchronization failed');
    }
    String reloadMessage = 'Reloaded 0 libraries';
    final Stopwatch reloadVMTimer = _stopwatchFactory.createStopwatch('reloadSources:vm')..start();
    final Map<String, Object> firstReloadDetails = <String, Object>{};
    if (updatedDevFS.invalidatedSourcesCount > 0) {
      final OperationResult result = await _reloadSourcesHelper(
        this,
        flutterDevices,
        pause,
        firstReloadDetails,
        targetPlatform,
        sdkName,
        emulator,
        reason,
      );
      if (result.code != 0) {
        return result;
      }
      reloadMessage = result.message;
    } else {
      _addBenchmarkData('hotReloadVMReloadMilliseconds', 0);
    }
    reloadVMTimer.stop();

    await evictDirtyAssets();

    final Stopwatch reassembleTimer = _stopwatchFactory.createStopwatch('reloadSources:reassemble')..start();

    final ReassembleResult reassembleResult = await _reassembleHelper(
      flutterDevices,
      viewCache,
      onSlow,
      reloadMessage,
      updatedDevFS.fastReassembleClassName,
    );
    shouldReportReloadTime = reassembleResult.shouldReportReloadTime;
    if (reassembleResult.reassembleViews.isEmpty) {
      return OperationResult(OperationResult.ok.code, reloadMessage);
    }
    // Record time it took for Flutter to reassemble the application.
    reassembleTimer.stop();
    _addBenchmarkData('hotReloadFlutterReassembleMilliseconds', reassembleTimer.elapsed.inMilliseconds);

    reloadTimer.stop();
    final Duration reloadDuration = reloadTimer.elapsed;
    final int reloadInMs = reloadDuration.inMilliseconds;

    // Collect stats that help understand scale of update for this hot reload request.
    // For example, [syncedLibraryCount]/[finalLibraryCount] indicates how
    // many libraries were affected by the hot reload request.
    // Relation of [invalidatedSourcesCount] to [syncedLibraryCount] should help
    // understand sync/transfer "overhead" of updating this number of source files.
    HotEvent('reload',
      targetPlatform: targetPlatform,
      sdkName: sdkName,
      emulator: emulator,
      fullRestart: false,
      reason: reason,
      overallTimeInMs: reloadInMs,
      finalLibraryCount: firstReloadDetails['finalLibraryCount'] as int ?? 0,
      syncedLibraryCount: firstReloadDetails['receivedLibraryCount'] as int ?? 0,
      syncedClassesCount: firstReloadDetails['receivedClassesCount'] as int ?? 0,
      syncedProceduresCount: firstReloadDetails['receivedProceduresCount'] as int ?? 0,
      syncedBytes: updatedDevFS.syncedBytes,
      invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount,
      transferTimeInMs: updatedDevFS.transferDuration.inMilliseconds,
      fastReassemble: featureFlags.isSingleWidgetReloadEnabled
        ? updatedDevFS.fastReassembleClassName != null
        : null,
      compileTimeInMs: updatedDevFS.compileDuration.inMilliseconds,
      findInvalidatedTimeInMs: updatedDevFS.findInvalidatedDuration.inMilliseconds,
      scannedSourcesCount: updatedDevFS.scannedSourcesCount,
      reassembleTimeInMs: reassembleTimer.elapsed.inMilliseconds,
      reloadVMTimeInMs: reloadVMTimer.elapsed.inMilliseconds,
    ).send();

    if (shouldReportReloadTime) {
      globals.printTrace('Hot reload performed in ${getElapsedAsMilliseconds(reloadDuration)}.');
      // Record complete time it took for the reload.
      _addBenchmarkData('hotReloadMillisecondsToFrame', reloadInMs);
    }
    // Only report timings if we reloaded a single view without any errors.
    if ((reassembleResult.reassembleViews.length == 1) && !reassembleResult.failedReassemble && shouldReportReloadTime) {
      globals.flutterUsage.sendTiming('hot', 'reload', reloadDuration);
    }
    return OperationResult(
      reassembleResult.failedReassemble ? 1 : OperationResult.ok.code,
      reloadMessage,
    );
  }

请添加图片描述
我们点进去跟进一下,可以看到他在遍历设备去更新
请添加图片描述
继续点进去
请添加图片描述
请添加图片描述
这里我们看到content是一个路径,我们不妨去找到它
请添加图片描述
我们来用终端看他里面是什么东西
请添加图片描述
是咱们main.dart,我们在来去别的文件夹改变一下代码再来看看,r执行热重载
请添加图片描述
变了,真的变了,这个文件就是我们刚才改过的文件,而且只有我们改过的文件(可以多改几个文件试试),这证明了flutter热重载是在找到被修改的文件,去增量更新。我们继续向下看代码。
请添加图片描述
_httpWriter是虚拟机的地址,这里将增量的文件上传到了DartVM虚拟机,然后DartVM利用RPC协议(底层是sockt)调用reloadsource进行资源重载,有兴趣的同学可以再点进去看看,这里就不过多介绍了。

到这里就已经跟不下去了,已经传输到了虚拟机,然后虚拟机会发送给Flutter引擎,这里我们利用flutter引擎(引擎源码下载以及转译较为繁琐,小编有空会为大家更新)挂载去调试一下看看。

挂载Flutter引擎调试

打开挂载工程iOS的Runner
请添加图片描述
再回到flutter_tools去配置一下,run --local-engine-src-path /Users/wangmeng/Desktop/gogogo/engine_download/src --local-engine=ios_debug_sim_unopt
请添加图片描述
运行,xcode添加调试现有工程,Debug -> Attach to process,选择你的工程,直接下断点,br set -n "IsolateGroupReloadContext::Reload"
请添加图片描述
下断点成功,那么我们直接去flutter_tools执行热重载(记得去更改一下代码,不然断点不会走)
请添加图片描述
果然来到了这里,flutter引擎接收到了增量文件,触发widgets的重新建立布局绘制(),服务触发后,BindingBase.reassembleApplication-> WidgetsBinding. performReassemble -> BuildOwner.reassemble -> Element.reassemble 由根节点开始一步步实现widgets树重建。

总结

Flutter在执行热重载的时候,首先会将工程文件逐一扫描,检查是否有删除/新增等改动,扫描完成后生成pp.dill.incremental.dill文件,通过http端口发送给Dart虚拟机,资源重载(RPC协议),重载成功之后,将FlutterDevice UI线程重置,通过RPC接口,触发flutter widgets树重建、重绘。
请添加图片描述

调试的篇幅较长,笔者只截取了自认为重点的部分,有不当之处,希望给笔者及时指正,或者私信我,共同探索flutter原理。

参考文档
揭秘Flutter Hot Reload(原理篇)
  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值