做者:閒魚技術-君愛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