做者:闲鱼技术-君爱android
闲鱼技术团队在2018年引入Flutter后,愈来愈多的业务场景在Flutter上使用。Flutter的亚秒级热重载一直是开发者的神兵利器,提供给开发者快速修改UI,增长功能,修复bug,不须要从新启动应用,便可看到改动效果。ios
热重载(HotReload)究竟是如何实现的呢?git
本文带你一步步揭开Hot Reload神秘面纱。github
想了解HotReload如何运行,首先,咱们须要掌握flutter_tools的调试方法。shell
咱们建立一个名为fluttertest的简单flutter项目做为例子。编程
使用AndroidStudio打开flutter_tools(/flutter/packages/flutter_tools),断点设置为HotRunner.restart()方法app
添加新的Debug Configurations,woking directory设置为fluttertest项目地址ide
触发flutter_tools debug按钮,待app启动后,简单改动fluttertest代码源码分析
在flutter_tools Debug Console中输入r,开始调试。布局
断点成功!
###2.1 HotReload基本流程
那么HotReload如何运行呢?
当咱们使用运行HotReload,不管是经过控制台输入r启动,或是点击闪电运行,最终是运行flutter_tools中的HotRunner.restart(fullRestart: false)方法(上文断点处)。
restart()方法中,调用了_reloadSources(pause: pauseAfterRestart),正是HotReload的主要代码之处。
(源码位于/flutter/packages/flutter_tools/lib/src/run_hot.dart)
Future<OperationResult> _reloadSources({ bool pause = false })
复制代码
_reloadSources方法中:
理解这个流程,前提须要明确Flutter的编译模式。
编译模式大致能够分为两种,AOT编译与JIT编译。JIT全称是Just In Time,代码能够在程序执行时期编译,由于要在程序执行前进行分析、编译,JIT编译可能会致使程序执行时间较慢;而AOT编译,全称Ahead Of Time,是在程序运行前就已经编译,从开发者修改代码、编译较慢,但运行时不须要进行分析、编译,所以执行速度更快。
Flutter使用了独特的编译模式,开发阶段下,使用Kernel Snapshot模式(对应JIT编译),将dart代码生成标记化的源代码,运行时编译,解释执行;release阶段,ios使用AOT编译,编译器将dart代码生成汇编代码,最终生成app.framwork,android使用了Core JIT编译,dart转化为二进制模式,在VM启动前载入。
所以,基于开发阶段的Kernel Snapshot编译模式下,咱们能够得知Hot Reload扫描项目文件,将有改动的dart文件转化为标记化源代码kernel files,发送到正在运行的DartVM,DartVM替换资源,而后通知Flutter Framework重建、从新布局、从新绘制WidgetsTree,便可看到改动效果。
到这里,咱们已经了解HotReload基本运行流程,但app.dill.incremental.dill是怎样的文件,又怎么和旧文件替换的呢?
在启动应用后,启动HotReload以前,编译成功后,项目目录/fluttertest/build文件中,自动生成了app.dill文件。 经过strings命令解析,发现是标记化的源代码,其中包含完整的业务代码。
(篇幅较长,只截取了一部分)
同时,经过adb shell检查,发现设备中/data/data/com.loommo.fluttertest/com.loommo.fluttertest/app_flutter/flutter_assets下,生成三个文件;
其中,kernel_blob.bin经过strings命令解析,发现内容与app.dill一致;
首次启动应用后,生成的完整的业务代码文件app.dill,在设备上体现为kernel_blob.bin;
咱们启动HotReload,_updateDevFS()这一步骤执行完毕后,
(源码位于/flutter/packages/flutter_tools/lib/src/devfs.dart)
Future<int> update({@required String mainPath,String target,AssetBundle bundle,DateTime firstBuildTime,bool bundleFirstUpload = false,bool bundleDirty = false,Set<String> fileFilter,@required ResidentCompiler generator,String dillOutputPath,bool fullRestart = false,String projectRootPath,@required String pathToReload,})
复制代码
检查项目,能够发现项目目录/fluttertest/build/下新增了app.dill.incremental.dill文件,经过strings命令解析后,发现仅包含咱们所改动的dart文件。
同时,经过adb shell检查,发现设备中/data/data/com.loommo.fluttertest/cache/fluttertestYAYDGJ/fluttertest/lib下,也增长了一个main.dart.incremental.dill ,经过strings命令解析。
果真,与app.dill.incremental.dill内容一致。
而/data/data/com.loommo.fluttertest/com.loommo.fluttertest/app_flutter/flutter_assets/kernel_blob.bin 没有改变。
上文中能够知道Flutter Tools生成app.dill.incremental.dill文件后,经过RPC调用_reloadSources,实际触发的是,Flutter Engine中DartVM Reload方法,该方法中,对.incremental.dill进行增量编译,替换编译产物,实现改动文件的更新。
(源码位于/engine/src/third_party/dart/runtime/vm/isolate_reload.cc)
void IsolateReloadContext::Reload(bool force_reload,const char* root_script_url,const char* packages_url_)
复制代码
后续文章继续深刻分析,有兴趣的同窗能够先仔细阅读源码。
从上文咱们能够知道,Hot reload将资源重载完成后,通知flutter framework,触发widgets树的从新创建、从新布局、从新绘制。
那么,flutter是如何触发widgets树的重建呢?
Flutter framework中BindingBase注册了名为reassemble的Dart VM服务,用于外部与正在运行的Dart VM通讯,可以触发根节点树重建操做。
服务触发后,BindingBase.reassembleApplication-> WidgetsBinding. performReassemble -> BuildOwner.reassemble -> Element.reassemble 由根节点开始一步步实现widgets树重建。
(源码位于/flutter/packages/flutter/lib/src/foundation/binding.dart)
Future<Null> reassembleApplication()
复制代码
Flutter不一样于以往Native开发,广受赞誉的,其一即是亚秒级热重载,理解HotReload的原理,有助于辅助咱们平常开发,更为后续动态化方案提供理论支持。
闲鱼技术团队是一只短小精悍的工程技术团队。咱们不只关注于业务问题的有效解决,同时咱们在推进打破技术栈分工限制(android/iOS/Html5/Server 编程模型和语言的统一)、计算机视觉技术在移动终端上的前沿实践工做。做为闲鱼技术团队的软件工程师,您有机会去展现您全部的才能和勇气,在整个产品的演进和用户问题解决中证实技术发展是改变生活方式的动力。 简历投递:guicai.gxy@alibaba-inc.com