目录

一、原理

二、源码详解

2.1 扫描代码改动

2.2 编译生成增量文件

2.3 推送更新

2.4 触发reload

2.5 加载编译增量文件

2.6 WidgetsTree重建

三、开始问题结论


HotReload是指,在不中断 App 正常运行的情况下,动态注入修改后的代码片段,不需要重新编译、Install App。Flutter HotReload只能在 Debug 模式下使用,是因为 Debug 模式下,Flutter 采用的是 JIT(即时编译或运行时编译) 编译, Release 模式下采用的是 AOT(提前编译或运行前编译) 静态编译。

本文详解Flutter HotReload原理,并回答以下几个问题:

1、混合栈开发的情况下,dart代码通常会被打成平台包(Android:aar,Ios:FrameWork)App进行依赖,此时可以hotReload吗?

2、Flutter HotReload时的增量文件生成的依据是什么?多次HotReload时每次增量文件的生成依赖的第一次还是上一次?

3、为什么很多场景下HotReload不会生效?重启App,再次Attach时为什么之前HotReload的文件没有生效?

4、为什么增量文件中是被修改的dart文件整个文件的内容而不是只有diff内容?

一、原理

Flutter HotReload主要有两大部分:

第一部分:生成增量文件并推送。主要流程: 改动文件的扫描 -> 编译生成增量文件 -> 推送更新。在开发环境上(比如PC)由flutter_tools来完成。

第二部分:编译增量文件,重建widget树。主要流程: 增量文件编译 ->Widget 重建。 在运行flutter项目的device上,由Dart VM和Flutter Engine来完成。

1、扫描工程代码改动。热重载模块会逐一扫描工程中的文件,检查是否有新增、删除或者改动,直到找到在上次编译之后,发生变化的 Dart 代码。

对比的依据是上次编译时的工程文件列表,文件路径缓存在flutterDevice.sources中。attach或者run flutter工程时flutter_tools会运行dart命令来获取初时的列表,后续每次hotRload时会更新文件flutterDevice.sources。 是否改动的算法是:根据lastCompiled时间及每个文件的modified时间来判断。

2、编译生成增量文件。

所有改动的文件会被编译生成app.dill.incremental.dill文件,mac上app.dill.incremental.dill文件可以通过strings 命令查看,其中记录了改动文件的路径以及文件内容(整个文件不是diff)。

3、推送更新。

flutter_tools将增量的 Dart Kernel 文件通过 HTTP 端口,发送给正在移动设备上运行的 Dart VM。

4、触发reload

flutter_tools通过vm service与dart vm进行通信。推送更新成功后会触发reloadSources服务通知Dart Vm.

5、编译增量文件。

Dart VM监听到reloadSources的通知后会加载增量文件,并编译。

6、Widget 重建。

Flutter Framework会注册reassemble服务。Dart VM 资源加载成功后,会通过VM Service 通知 Flutter Framework 重建 Widget。

二、源码详解

flutter_tools中使用flutterDevice来记录每个正在attach的设备,通过vmService来与Dart Vm进行通信。flutterDevice中记录了flutter工程的所有源文件路径、Flutter工程上次编译的时间、vmService地址、设备中增量文件写入路径等

2.1 扫描代码改动

attach 或者run Flutter工程时flutter_tools会运行以下命令compile当前Flutter工程并将文件列表并缓存在flutterDevice.devFs.sources中,hotReload时依据sources中的文件列表逐个判断每个文件是否有更改。每次horReload时会重新运行以下命令,并更新flutterDevice.sources列表。

2.2 编译生成增量文件

hot reload时会判断flutterDevice.devFs.sources中的所有文件是否有更新,来获取更改的文件列表。其中根据工程的上次编译时间lastCompiled以及每个文件的modified时间来判断文件是否有更新。

//run_hot.dart
//urisToScan为flutter工程中的文件列表
Future<InvalidationResult> findInvalidated(...) async {...for (final Uri uri in urisToScan) {final DateTime updatedAt = uri.hasScheme && uri.scheme != 'file'? _fileSystem.file(uri).statSync().modified: _fileSystem.statSync(uri.toFilePath(windows: _platform.isWindows)).modified;if (updatedAt != null && updatedAt.isAfter(lastCompiled)) {invalidatedFiles.add(uri);}}...
};

recompile生成main.dill.incremental.dill文件。

 Future<UpdateFSReport> update(...) async {...//重新编译final Future<CompilerOutput?> pendingCompilerOutput = generator.recompile(mainUri,invalidatedFiles,outputPath: dillOutputPath,fs: _fileSystem,projectRootPath: projectRootPath,packageConfig: packageConfig,checkDartPluginRegistry: true, // The entry point is assumed not to have changed.).then((CompilerOutput? result) {compileTimer.stop();return result;});final CompilerOutput? compilerOutput = await pendingCompilerOutput;_previousCompiled = lastCompiled;lastCompiled = candidateCompileTime;// list of sources that needs to be monitored are in [compilerOutput.sources]//下次hotreload会依据sources来进行比较sources = compilerOutput.sources;//// Don't send full kernel file that would overwrite what VM already// started loading from.if (!bundleFirstUpload) {final String compiledBinary = compilerOutput.outputFilename;if (compiledBinary.isNotEmpty) {final Uri entryUri = _fileSystem.path.toUri(pathToReload);final DevFSFileContent content = DevFSFileContent(_fileSystem.file(compiledBinary));syncedBytes += content.size;dirtyEntries[entryUri] = content;}}......    }

2.3 推送更新

增量的dill文件通过http(deice->devfs中有记录http地址)发送至device

// devfs.dartFuture<UpdateFSReport> update(...) async {//推送更新至设备if (dirtyEntries.isNotEmpty) {await (devFSWriter ?? _httpWriter).write(dirtyEntries, _baseUri!, _httpWriter);}}

2.4 触发reload

推送更新成功后,如果有更新的文件,会通过VM Service通知dart vm reloadSources。其中发送的数据格式为json。

//run_hot.dart
Future<List<Future<vm_service.ReloadReport>>> _reloadDeviceSources(FlutterDevice device,String entryPath, {bool pause = false,
}) async {final String deviceEntryUri = device.devFS.baseUri.resolve(entryPath).toString();final vm_service.VM vm = await device.vmService.service.getVM();return <Future<vm_service.ReloadReport>>[for (final vm_service.IsolateRef isolateRef in vm.isolates)//调用vm_service中的reloadSourcesdevice.vmService.service.reloadSources(isolateRef.id,pause: pause,rootLibUri: deviceEntryUri,)];
}//vm_service.dart
Future<ReloadReport> reloadSources(String isolateId, {bool? force,bool? pause,String? rootLibUri,String? packagesUri,
}) =>_call('reloadSources', {'isolateId': isolateId,if (force != null) 'force': force,if (pause != null) 'pause': pause,//file:///data/user/0/com.xxx.xxx/code_cache/dartmodulexxx/dartmodule/main.dart.incremental.dillif (rootLibUri != null) 'rootLibUri': rootLibUri,if (packagesUri != null) 'packagesUri': packagesUri,});Future<T> _call<T>(String method, [Map args = const {}]) async {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;
}

Dart Vm中注册了reloadSources的service。收到“reloadSources”时会调用ReloadSources()函数,最终在IsolateGroupReloadContext::Reload()函数中加载增量文件并编译。

//runtime/vm/service.cc
static const ServiceMethodDescriptor service_methods_[] = {......{ "invoke", Invoke, invoke_params },{ "kill", Kill, kill_params },{ "pause", Pause,pause_params },{ "reloadSources", ReloadSources,reload_sources_params },{ "_reloadSources", ReloadSources,reload_sources_params },......
};static void ReloadSources(Thread* thread, JSONStream* js) {......IsolateGroup* isolate_group = thread->isolate_group();if (isolate_group->IsReloading()) {return;}isolate_group->ReloadSources(js, force_reload, js->LookupParam("rootLibUri"),js->LookupParam("packagesUri"));......
}
//runtime/vm/isolate.cc
bool IsolateGroup::ReloadSources(JSONStream* js,bool force_reload,const char* root_script_url,const char* packages_url,bool dont_delete_reload_context) {......std::shared_ptr<IsolateGroupReloadContext> group_reload_context(new IsolateGroupReloadContext(this, shared_class_table, js));group_reload_context_ = group_reload_context;const bool success =group_reload_context_->Reload(force_reload, root_script_url, packages_url,/*kernel_buffer=*/nullptr,/*kernel_buffer_size=*/0);return success;}

2.5 加载编译增量文件

//runtime/vm/isolate.cc
//root_script_url为dill文件路径
bool IsolateGroupReloadContext::Reload(bool force_reload,const char* root_script_url,const char* packages_url,const uint8_t* kernel_buffer,intptr_t kernel_buffer_size) {// Load the kernel program and figure out the modified libraries.//加载增量文件intptr_t* p_num_received_classes = nullptr;intptr_t* p_num_received_procedures = nullptr;// ReadKernelFromFile checks to see if the file at// root_script_url is a valid .dill file. If that's the case, a Program*// is returned. Otherwise, this is likely a source file that needs to be// compiled, so ReadKernelFromFile returns NULL.kernel_program = kernel::Program::ReadFromFile(root_script_url);if (kernel_program != nullptr) {num_received_libs_ = kernel_program->library_count();bytes_received_libs_ = kernel_program->kernel_data_size();p_num_received_classes = &num_received_classes_;p_num_received_procedures = &num_received_procedures_;} ......//编译增量文件IsolateGroupSource* source = IsolateGroup::Current()->source();source->add_loaded_blob(Z, external_typed_data);modified_libs_ = new (Z) BitVector(Z, num_old_libs_);kernel::KernelLoader::FindModifiedLibraries(kernel_program.get(), IG, modified_libs_, force_reload, &skip_reload,p_num_received_classes, p_num_received_procedures);modified_libs_transitive_ = new (Z) BitVector(Z, num_old_libs_);BuildModifiedLibrariesClosure(modified_libs_);......
}

2.6 WidgetsTree重建

Flutter framework中BindingBase注册了名为reassemble的Dart VM服务,用于外部与正在运行的Dart VM通信,能够触发根节点树重建操作。

服务触发后,BindingBase.reassembleApplication-> WidgetsBinding. performReassemble -> BuildOwner.reassemble -> Element.reassemble 由根节点开始一步步实现widgets树重建。

//src/foundation/binding.dart
void initServiceExtensions() {//注册reassemble服务registerSignalServiceExtension(name: 'reassemble',callback: reassembleApplication,);
}Future<void> reassembleApplication() {//执行performReassemblereturn lockEvents(performReassemble);}    

三、开始问题结论

1、混合栈开发的情况下,dart代码通常会被打成平台包(Android:aar,Ios:FrameWork)App进行依赖,此时可以hotReload吗?

可以。有运行的dart vm即可以attach flutter工程成功。但是hotreload时的增量文件生成是依赖与开发机器上的dart工程代码,hot reload可能会不成功。比如aar是版本1,attach时本地代码是版本2,hotreload时的代码时版本3。horrelaod生成的main.dill.incremental.dill文件中包含的时版本3与版本2的差异文件的内容。

2、Flutter HotReload时的增量文件生成的依据是什么?多次HotReload时每次增量文件的生成依赖的第一次还是上一次?

attach或者run flutter app时编译时的文件列表。上一次。

3、重启App,再次Attach时为什么之前HotReload的文件没有生效?

app.dill文件与main.dill.incremental.dill文件只会在内存中进行合并,不会在dill文件中进行合并,再次启动时加载的是main.dill.incremental.dill文件。

4、为什么增量文件中是被修改的dart文件整个文件的内容而不是只有diff内容?

dart vm会加载并编译main.dill.incremental.dill增量文件,然后重新构建widget。不会进行dill文件与main.dill.incremental.dill文件的合并。

Flutter HotRealod详解相关推荐

  1. Flutter Widget详解

    Flutter Widget详解 Widget是Flutter应用程序用户界面的基本构建块.每个Widget都是用户界面一部分的不可变声明. 与其他将视图.控制器.布局和其他属性分离的框架不同,Flu ...

  2. 学习笔记-Flutter 动画详解(一)

    Flutter 动画详解(一) 本文主要介绍了动画的原理相关概念,对其他平台的动画做了一个简要的梳理,并简要的介绍了Flutter动画的一些知识. 1. 动画介绍 动画对于App来说,非常的重要.很多 ...

  3. Flutter Text详解

    示例 API Text,很常用的一个Widget:用于显示简单样式文本,它包含一些控制文本显示样式的一些属性 text构造方法源码: /// If the [style] argument is nu ...

  4. Flutter ListView详解

    ListView详解 ListView常用构造 ListView ListView 默认构建 效果 ListView ListTile ListTile 属性 ListTile 使用 效果 ListV ...

  5. [flutter专题]详解AppBar小部件

    大家好,我是坚果,公众号"坚果前端" AppBar 应用栏是各种应用程序中最常用的组件之一.它可用于容纳搜索字段.以及在页面之间导航的按钮,或者只是页面标题.由于它是一个如此常用的 ...

  6. Flutter安装详解 as版本

    1 第一步下载flutter sdk 选择我们需要下载flutter sdk 的目录鼠标右键打开 Git Bash Here 输入以下文字 点击回车 等待下载完成即可. git clone -b al ...

  7. Flutter TextField详解

    文章目录 基本属性 TextField InputDecoration 样式 基础样式 隐藏文本 键盘类型 键盘按钮 大小写 光标 最多行数 计数器 图标 提示文字 去除下划线 边框 获取输入内容 关 ...

  8. Flutter Drawer详解

    文章目录 简介 基础属性 DrawerHeader UserAccountsDrawerHeader 功能列表 Drawer打开关闭 完整代码 效果: 简介 Drawer是一个抽屉控件,通常从页面的左 ...

  9. Flutter GridView详解

    GridView GridView常用构造 GridView 构造函数 GridView.count 构造函数 分析和使用 GridView.extent 构造函数 分析和使用 GridView.bu ...

  10. Flutter Chip详解

    文章目录 Chip ActionChip ChoiceChip 示例代码: 效果: Chip 标签 使用场景:事物的属性或标签,历史搜索记录等. const Chip({Key key,this.av ...

最新文章

  1. 浓缩摘要_浓缩咖啡的收益递减
  2. Python3错误和异常
  3. keras padding_GAN整体思路以及使用Keras搭建DCGAN
  4. 产业链人士:部分客户订单减少 联发科四季度营收可能环比下滑
  5. 开源GIS(二十)——CAD数据添加属性转GIS数据
  6. c++对象模型大总结:第1-4章、对象初探与构造函数
  7. 《Javascript DOM 编程艺术》
  8. 2004年考研数学一真题解析pdf
  9. 注塑成型工艺中的背压到底有多重要?
  10. 清明忆语 | 缅怀那些正渐行渐远的编程语言
  11. Cardboard的学习(二)Cardboard的下载与导入
  12. MATLAB中柱形图的绘制
  13. python地图实例_利用pyecharts实现地图可视化的例子
  14. 基于FPGA的RISC_V五级流水设计---存储设计
  15. 2020车载凯立德懒人包下载_华为HarmonyOS App开发工具DevEco Studio下载安装及第一个HarmonyOS App实战教程...
  16. 如何编写项目总结报告
  17. 新增大论坛WPS Office
  18. #TP4056#--3.7V锂电池充放电电路(实践日志篇)
  19. 5G成互联网大会最热关键词!
  20. SDOI2017 数字表格

热门文章

  1. PR期刊投稿要求(整体要求、Title page、Highlight)
  2. 自控力:和压力做朋友(斯坦福大学实用的心理学课程) 读后感
  3. SpringBoot 通用项目配置
  4. JavaScript登录界面制作
  5. 感冒究竟能不能喝咖啡
  6. 《我奋斗了18年才和你坐在一起喝咖啡》,而我奋斗了18年,不是为了和你一起喝咖啡(转载)...
  7. 龙芯3a5000下编译postgresql 14.3
  8. modelsim 波形设置显示时间单位
  9. 打印CSDN网页内容
  10. 模电_第六章_负反馈电路