studio 热重载应用_深入Flutter热重载机制

Flutter对于开发者来说一个很大的亮点就是它的热重载功能。热重载能帮助我们在无需重启整个应用的情况下快速调整UI,新增功能,修复bug等,能很大程度的节省开发时间,提升效率。Flutter是怎么做到这样快速更新的呢?要理解热重载的实现机制,我们先来了解下Flutter的编译模式。 Flutter编译模式

编译模式主要分为两大类:JIT和AOT。

JIT:Just in time,即时编译,程序执行过程中边翻译边运行,启动速度快,但执行效率相对于AOT会低一些。

AOT:Ahead of time,提前编译,程序在执行前全部被翻译成机器码,减少了运行时的性能损耗,运行更流畅。

Flutter结合了这两种编译方式,开发阶段采用JIT,Release阶段采用AOT。开发阶段边翻译边运行的方式正是热重载功能的前提,运行过程中将有变更的代码文件注入到Dart虚拟机中,虚拟机加载新的文件后,触发重建widget树,从而使开发者能快速看到更新后的效果。

接下来我们结合flutter tools源码深入了解下热重载机制:

Flutter tools

Flutter tools 本质上是一个Dart程序,位于path/to/flutter/packages/flutter_tools路径下,它的入口函数是flutter_tools.dart的main方法:

import 'package:flutter_tools/executable.dart' as executable; void main(List args) { executable.main(args); }

executable.main(args)中组装了很多种类型的FlutterCommand:

await runner.run(args, [ AnalyzeCommand(verboseHelp: verboseHelp), AssembleCommand(), AttachCommand(verboseHelp: verboseHelp), BuildCommand(verboseHelp: verboseHelp), ...... RunCommand(verboseHelp: verboseHelp), ...... UpgradeCommand(), VersionCommand(), ], ...... }

然后在CommandRunner的

Future runCommand(ArgResults topLevelResults)

方法中根据条件遍历执行这些FlutterCommand的runCommand方法,直到有FlutterCommand处理此次命令。重点了解一下RunCommand,RunCommand处理flutter run命令,在它的runCommond方法中会根据hotMode来判断创建HotRunner或ColdRunner(如果不强制指定--no-hot,在debug模式下默认是启用hotMode的),然后执行所创建的HotRunner/ColdRunner的run方法。

要了解HotRunner,我们先来了解几个类的主要功能:

VMService:到设备Dart VM的一个连接

_DevFSHttpWriter:通过VMService给出的httpAddress地址,负责向设备指定目录写文件

ResidengCompiler:负责文件编译,生成CompilerOutput对象

DevFS:使用ResidengCompiler编译生成变更代码(新增,删除,修改)的结果文件,然后通过_DevFSHttpWriter同步到device设备上。

为更直观的了解代码流程,结合场景断点查看(flutter tools的详细断点方法可参见文末附录)。

场景:

创建测试工程t_flutter_app,效果如下:

简单修改工程中main.dart的代码 Text('You have pushed the button this many times:',) 修改为Text('You have pushed the button this many times 123:',)

控制台输入r后查看devFS值:

_baseUri为设备上对应的目录地址。rootDirectory为项目本地代码路径,sources为监听变更的代码文件集合,_httpWriter中的httpAddress为连接到设备的地址,值得一提的是,我们常用的Observatory功能也是基于这个地址

那变更代码集合是怎么生成的呢,跟踪到flutter_tools/lib/src/run_hot.dart中的_updateDevFS方法,看相关代码:

final List invalidatedFiles = ProjectFileInvalidator.findInvalidated( lastCompiled: flutterDevices[0].devFS.lastCompiled, urisToMonitor: flutterDevices[0].devFS.sources, packagesPath: packagesFilePath, );

监听的文件集合就是上面提到的devFS.sources。ProjectFileInvalidator.findInvalidated方法检测文件变更,其判断条件为

updatedAt.millisecondsSinceEpoch > lastCompiled.millisecondsSinceEpoch

,即在上次编译之后文件有更新。通过该方法得到List invalidatedFiles也就是变更代码集合,此场景下仅有一个文件变更,Uri为file:///Users/heq/workspace/t_flutter_app/lib/main.dart。

找到变更代码集合后,compile生成CompileOutput对象

可以看到这个输出文件路径为 t_flutter_app/build/app.dill.incremental.dill。然后包装这个CompileOutput对象到Map dirtyEntries中,并通过_httpWriter将数据同步到设备上,查看dirtyEntries内容:

可以发现本地的t_flutter_app/build/app.dill.incremental.dill和设备上的/data/data/com.example.t_flutter_app/code_cache/t_flutter_appLAGDOT/t_flutter_app/lib/main.dart.incremental.dill是匹配的。.dill文件内容可通过strings命令查看 如 strings app.dill.incremental.dill

Dart VM 收到增量Kernel文件重载成功后,触发flutter widgets树的重建,这里主要是触发Element元素的reassemble方法。

abstract class Element extends DiagnosticableTree implements BuildContext { ...... @protected void reassemble() { markNeedsBuild(); visitChildren((Element child) { child.reassemble(); }); } }

可简单看下此时reassemble的调用栈

即 BindingBase.reassembleApplication ->WidgetsBinding.performReassemble ->BuildOwner.reassemble ->Element.reassemble 触发widget树的更新

热重载整体流程

总结上述内容,热重载基本流程为:

ProjectFileInvalidator.findInvalidated扫描项目工程文件,找到变更(新增,删除,修改)文件集合存储到List invalidatedFiles

使用generator.recompile编译invalidatedFiles,生成增量Dart Kernel文件app.dill.incremental.dill

通过_DevFSHttpWriter将增量Dart Kernel文件发送给设备Dart VM

Dart VM收到增量Dart Kernel文件后,与之前的Dart Kernel文件进行合并,然后重新加载新的Dart Kernel文件

重载成功后,触发flutter widgets树的重建

基本流程如下图:

热重载无效场景

根据上面的分析,使用热重载时,Flutter应用并未重新启动,而只是触发重建了widget树,需要注意的是1)此时State状态会被保留 2)只会根据原来的根节点来重建Widget树。所以热重载在以下这些典型场景下不会生效:

全局变量和静态属性的更改

main中代码逻辑更改,如:void main(){ //新增代码逻辑 runApp(MyApp()); } 修改Widget树的根节点,如:void main(){ runApp(MyApp()); //变更为 runApp(const Center( child: const Text('Hello', textDirection: TextDirection.ltr))); } widget的Stateless和Stateful状态变化,如:class MyWidget extends StatelessWidge //变更为: class MyWidget extends StatefulWidget initState中的代码逻辑更改

更详细的说明可参考官方文档 Hot reload(https://flutter.dev/docs/development/tools/hot-reload)

针对上面一些hot reload不生效的场景,就需要使用hot restart。

hot reload和hot restart区别

hot reload和hot restart对应到了AndroidStudio工具栏上的两个按钮

另外,通过flutter tools启动Flutter应用后控制台中有提示

To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".

表示输入r执行hot reload,输入R执行hot restart。

这里的不同输入对应到TerminalHandler中_commonTerminalInputHandler的不同执行流程。具体代码位于flutter_tools/lib/src/resident_runner.dart

Future _commonTerminalInputHandler(String character) async { switch(character) { case 'r': ...... final OperationResult result = await residentRunner.restart(fullRestart: false); ...... case 'R': ...... final OperationResult result = await residentRunner.restart(fullRestart: true); ...... } return false; }

可以看到r和R后续的处理流程差异其实就是residentRunner.restart方法的fullRestart参数值差别,更具体的后续执行流程可自行查看代码。

hot reload和hot restart的主要区别有:

hot reload保留了state状态,而hot restart会重置所有state状态

hot reload执行generator.recompile方法的输出文件为build/app.dill.incremental.dill,而hot restart的输出文件为build/app.dill

hot restart比hot reload花费更多的时间附1:flutter tools打印日志

flutter tools源码中会有很多关键节点打印日志的地方,如:

printTrace('Sending reload events to ${device.device.name}');

这些日志能帮我们快速了解执行流程。但默认在控制台中是看不见这些日志输出的,需要做些简单修改。

flutter tools中的默认文件系统初始化位于flutter_tools/lib/src/context_runner.dart文件的runInContext方法中:

Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger()

非windows系统下默认使用StdoutLogger,所以根据需要修改StdoutLogger方法便可打印出flutter tools的关键日志,如:

@override void printTrace(String message) { //增加log打印 print('[FlutterTools] $message'); }

即可在控制台查看flutter tools相关日志,类似如下:

[FlutterTools] DevFS: Sync finished 3,071ms (!) [FlutterTools] Synced 0.9MB. [FlutterTools] Sending to VM service: _flutter.listViews({}) 另外,如果要使修改后的flutter tools在以后执行flutter命令时生效,只要删除path/to/flutter/bin/cache目录下的flutter_tools.snapshot文件,然后重新执行flutter命令即可生成新的flutter_tools工具相关产物。

附2:flutter tools的调试创建flutter测试工程,这里工程名为 t_flutter_app

AndroidStudio打开flutter_tools工程,目录地址 path/to/flutter/packages/flutter_tools

在flutter_tools工程中所需位置设上断点

添加Debug Configuration

debug方式运行flutter_tools工程,待t_flutter_app启动后,控制台显示

修改t_flutter_app部分代码后,控制台输入 r,等待代码执行到断点位置,即可看到断点成功

关于Flutter的热重载机制相关内容就介绍到这里,如有错误疏漏之处,烦请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值