揭祕Flutter Hot Reload(基礎篇)

 2018-10-19 13:01:00.0

1. 前言

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

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

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

2. 源碼分析

2.1 FlutterTools調試

想了解HotReload如何運行,首先,我們需要掌握flutter_tools的調試方法。

我們創建一個名爲fluttertest的簡單Flutter項目作爲例子。

使用AndroidStudio打開fluttertools(/flutter/packages/fluttertools),斷點設置爲HotRunner.restart()方法

添加新的Debug Configurations,woking directory設置爲fluttertest項目地址

觸發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/fluttertools/lib/src/runhot.dart)

 
   
   
   
  1. Future<OperationResult> _reloadSources({ bool pause = false })

_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文件。

通過strings命令解析,發現是標記化的源代碼,其中包含完整的業務代碼。

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

同時,通過adb shell檢查,發現設備中/data/data/com.loommo.fluttertest/com.loommo.fluttertest/appflutter/flutterassets下,生成三個文件;

其中,kernel_blob.bin通過strings命令解析,發現內容與app.dill一致;

首次啓動應用後,生成的完整業務代碼文件app.dill,在設備上體現爲kernel_blob.bin;

我們啓動HotReload,_updateDevFS()這一步驟執行完畢後,

(/flutter/packages/flutter_tools/lib/src/devfs.dart)

 
   
   
   
  1.  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/appflutter/flutterassets/kernel_blob.bin 沒有改變。

上文中可以知道Flutter Tools生成app.dill.incremental.dill文件後,通過RPC調用_reloadSources,實際觸發的是,Flutter Engine中DartVM Reload方法,該方法中,對.incremental.dill進行增量編譯,替換編譯產物,實現改動文件的更新。

(/engine/src/thirdparty/dart/runtime/vm/isolatereload.cc)

 
   
   
   
  1. 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通信,服務觸發後能夠觸發根節點樹重建操作。

服務觸發後,由根節點開始一步步實現widgets樹重建。

BindingBase.reassembleApplication-> WidgetsBinding. performReassemble -> BuildOwner.reassemble -> Element.reassemble 。

(/flutter/packages/flutter/lib/src/foundation/binding.dart)

 
   
   
   
  1. Future<Null> reassembleApplication()

3. 結語

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

文章來源:機器之心