揭祕Flutter Hot Reload(原理篇)

閒魚技術發表於2018-10-18

作者:閒魚技術-君愛

1. 前言

閒魚技術團隊在2018年引入Flutter後,越來越多的業務場景在Flutter上使用。Flutter的亞秒級熱過載一直是開發者的神兵利器,提供給開發者快速修改UI,增加功能,修復bug,不需要重新啟動應用,即可看到改動效果。

熱過載(HotReload)到底是如何實現的呢?

本文帶你一步步揭開Hot Reload神祕面紗。

2. 原始碼分析

2.1 FlutterTools除錯

想了解HotReload如何執行,首先,我們需要掌握flutter_tools的除錯方法。

我們建立一個名為fluttertest的簡單flutter專案作為例子。

揭祕Flutter Hot Reload(原理篇)

使用AndroidStudio開啟flutter_tools(/flutter/packages/flutter_tools),斷點設定為HotRunner.restart()方法

揭祕Flutter Hot Reload(原理篇)

新增新的Debug Configurations,woking directory設定為fluttertest專案地址

揭祕Flutter Hot Reload(原理篇)

觸發flutter_tools debug按鈕,待app啟動後,簡單改動fluttertest程式碼

揭祕Flutter Hot Reload(原理篇)

在flutter_tools Debug Console中輸入r,開始除錯。

揭祕Flutter Hot Reload(原理篇)

斷點成功!

###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 })
複製程式碼

揭祕Flutter Hot Reload(原理篇)

_reloadSources方法中:

  1. 首先_updateDevFS()會將工程中檔案逐一掃描,檢查是否有刪除、新增或者改動,掃描完成後,生成kernel files,命名為app.dill.incremental.dill檔案,通過HTTP埠傳送給DartVM;
  2. 將掃描生成的.dill檔案路徑,通過RPC介面呼叫_reloadSources,進行資源載入;
  3. 確認VM資源過載成功,將FlutterDevice UI執行緒重置,通過RPC介面,觸發flutter widgets樹重建、重繪

理解這個流程,前提需要明確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是怎樣的檔案,又怎麼和舊檔案替換的呢?

2.2 增量程式碼掃描

在啟動應用後,啟動HotReload之前,編譯成功後,專案目錄/fluttertest/build檔案中,自動生成了app.dill檔案。 揭祕Flutter Hot Reload(原理篇) 通過strings命令解析,發現是標記化的原始碼,其中包含完整的業務程式碼。

揭祕Flutter Hot Reload(原理篇)

(篇幅較長,只擷取了一部分)

同時,通過adb shell檢查,發現裝置中/data/data/com.loommo.fluttertest/com.loommo.fluttertest/app_flutter/flutter_assets下,生成三個檔案; 揭祕Flutter Hot Reload(原理篇)

其中,kernel_blob.bin通過strings命令解析,發現內容與app.dill一致; 揭祕Flutter Hot Reload(原理篇)

首次啟動應用後,生成的完整的業務程式碼檔案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檔案。

揭祕Flutter Hot Reload(原理篇)

同時,通過adb shell檢查,發現裝置中/data/data/com.loommo.fluttertest/cache/fluttertestYAYDGJ/fluttertest/lib下,也增加了一個main.dart.incremental.dill ,通過strings命令解析。

揭祕Flutter Hot Reload(原理篇)

果然,與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_)    
複製程式碼

後續文章繼續深入分析,有興趣的同學可以先仔細閱讀原始碼。

2.3 WidgetsTree重建

從上文我們可以知道,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 Hot Reload(原理篇)

3. 結語

Flutter不同於以往Native開發,廣受讚譽的,其一便是亞秒級熱過載,理解HotReload的原理,有助於輔助我們日常開發,更為後續動態化方案提供理論支援。

4. 參考文件

  1. Dart VM服務協議
  2. 深入理解flutter的編譯原理與優化
  3. Using Hot Reload
  4. 上述使用到的原始碼

加入閒魚,一起玩些“酷”的

閒魚技術團隊是一隻短小精悍的工程技術團隊。我們不僅關注於業務問題的有效解決,同時我們在推動打破技術棧分工限制(android/iOS/Html5/Server 程式設計模型和語言的統一)、計算機視覺技術在移動終端上的前沿實踐工作。作為閒魚技術團隊的軟體工程師,您有機會去展示您所有的才能和勇氣,在整個產品的演進和使用者問題解決中證明技術發展是改變生活方式的動力。 簡歷投遞:guicai.gxy@alibaba-inc.com

相關文章