Flutter 熱過載

SunshineBrother發表於2021-03-15

概念

什麼是熱過載

熱過載是指,在不中斷 App 正常執行的情況下,動態注入修改後的程式碼片段。

  • Flutter 的熱過載功能可幫助您在無需重新啟動應用程式的情況下快速新增功能以及修復錯誤。通過將更新的原始碼檔案注入到正在執行的 Dart 虛擬機器(VM) 來實現熱過載。在虛擬機器使用新的欄位和函式更新類之後, Flutter 框架會自動重新構建 widget 樹,以便您可以快速檢視更改的效果
  • iOS 熱過載是通過注入動態庫的方式實現的

直譯器 & 編譯器

  • 直譯器:執行時才去解析程式碼
    • 直譯器會在執行時解釋執行程式碼,獲取一段程式碼後就會將其翻譯成目的碼(就是位元組碼(Bytecode)),然後一句一句地執行目的碼。直譯器,是在執行時才去解析程式碼,這樣就比在執行之前通過編譯器生成一份完整的機器碼再去執行的效率要低。
    • 直譯器可以在執行時去執行程式碼,說明它具有動態性,程式執行後能夠隨時通過增加和更新程式碼來改變程式的邏輯。
  • 編譯器:編譯時,編譯器把程式碼編譯成機器碼,然後直接在 CPU 上執行機器碼的
    • 連結器:最主要的作用,就是將符號繫結到地址上

那麼,使用編譯器和直譯器執行程式碼的特點,我們就可以概括如下

  • 採用編譯器生成機器碼執行的好處是效率高,缺點是除錯周期長。
  • 直譯器執行的好處是編寫除錯方便,缺點是執行效率低。

1594886745171-cd087e62-4dc4-4e7a-9c02-b728664b1911.png

Flutter編譯方式

Flutter在Debug和Relase執行不同的編譯模式

  • JIT:Just In Time . 動態解釋,一邊翻譯一邊執行,也稱為即時編譯,如JavaScript,Python等,在開發週期中使用,可以動態下發和執行程式碼,開發測試效率高,但是執行速度和效能則會受到影響,Flutter中的熱過載正是基於此特性

  • AOT: Ahead of Time. 靜態編譯,是指程式在執行前全部被翻譯為機器碼,提前編譯,如 C ,C++ ,OC等,釋出時期使用AOT,就不需要像RN那樣在跨平臺JavaScript程式碼和原生Android、iOS程式碼間建立低效的方法呼叫對映關係。

iOS App 如何通過注入動態庫的方式實現極速編譯除錯

現在蘋果公司使用的編譯器是 LLVMLLVM 是編譯器工具鏈技術的一個集合。而其中的 lldb 專案,就是內建連結器。

  • 編譯器會對每個檔案進行編譯,生成 Mach-O(可執行檔案)
  • 連結器會將專案中的多個 Mach-O 檔案合併成一個。

在真實的 iOS 開發中,你會發現很多功能都是現成可用的,不光你能夠用,其他 App 也在用,比如 GUI 框架、I/O、網路等。連結這些共享庫到你的 Mach-O 檔案,也是通過連結器來完成的

連結的共用庫分為靜態庫和動態庫

  • 1、靜態庫是編譯時連結的庫
    • 需要連結進你的 Mach-O 檔案裡,如果需要更新就要重新編譯一次,無法動態載入和更新
  • 2、動態庫是執行時連結的庫,使用 dyld 就可以實現動態載入。

Mach-O 檔案是編譯後的產物,而動態庫在執行時才會被連結,並沒參與 Mach-O 檔案的編譯和連結。

所以 Mach-O 檔案中並沒有包含動態庫裡的符號定義。也就是說,這些符號會顯示為“未定義”,但它們的名字和對應的庫的路徑會被記錄下來。執行時通過 dlopen 和 dlsym 匯入動態庫時,先根據記錄的庫路徑找到對應的庫,再通過記錄的名字元號找到繫結的地址。

dlopen 會把共享庫載入執行程式的地址空間,載入的共享庫也會有未定義的符號,這樣會觸發更多的共享庫被載入。dlopen 也可以選擇是立刻解析所有引用還是滯後去做。dlopen 開啟動態庫後返回的是引用的指標,dlsym 的作用就是通過 dlopen 返回的動態庫指標和函式符號,得到函式的地址然後使用

藉助Injection實現即時編譯

為了使iOS app能夠即時編譯,我們可以藉助一個工具Injection

Injection 是怎麼做到的呢

Injection 會監聽原始碼檔案的變化,如果檔案被改動了,Injection Server 就會執行 rebuildClass 重新進行編譯、打包成動態庫,也就是 .dylib 檔案。編譯、打包成動態庫後使用 writeSting 方法通過 Socket 通知執行的 App

1595244088796-72b8fc68-9d44-47d7-bc29-e3d598b307aa.png

Flutter 熱過載

2dfbedae7b95dd152a587070db4bb9fa_900x528.png

總體來說,熱過載的流程可以分為掃描工程改動、增量編譯、推送更新、程式碼合併、Widget 重建 5 個步驟:

  • 1、工程改動。熱過載模組會逐一掃描工程中的檔案,檢查是否有新增、刪除或者改動,直到找到在上次編譯之後,發生變化的 Dart 程式碼。
  • 2、增量編譯。熱過載模組會將發生變化的 Dart 程式碼,通過編譯轉化為增量的 Dart Kernel 檔案。
  • 3、推送更新。熱過載模組將增量的 Dart Kernel 檔案通過 HTTP 埠,傳送給正在移動裝置上執行的 Dart VM。
  • 4、程式碼合併。Dart VM 會將收到的增量 Dart Kernel 檔案,與原有的 Dart Kernel 檔案進行合併,然後重新載入新的 Dart Kernel 檔案。
  • 5、Widget 重建。在確認 Dart VM 資源載入成功後,Flutter 會將其 UI 執行緒重置,通知 Flutter Framework 重建 Widget

可以看到,Flutter 提供的熱過載在收到程式碼變更後,並不會讓 App 重新啟動執行,而只會觸發 Widget 樹的重新繪製,因此可以保持改動前的狀態,這就大大節省了除錯複雜互動介面的時間。

不支援熱過載的場景

Flutter 提供的亞秒級熱過載一直是開發者的除錯利器。通過熱過載,我們可以快速修改 UI、修復 Bug,無需重啟應用即可看到改動效果,從而大大提升了 UI 除錯效率。

不過,Flutter 的熱過載也有一定的侷限性。因為涉及到狀態儲存與恢復,所以並不是所有的程式碼改動都可以通過熱過載來更新。

接下來,我就與你介紹幾個不支援熱過載的典型場景:

  • 程式碼出現編譯錯誤;

  • Widget 狀態無法相容;

  • 全域性變數和靜態屬性的更改;

  • main 方法裡的更改;

  • initState 方法裡的更改;

  • 列舉和泛型別更改。

相關文章