當前位置:
首頁 > 最新 > Flutter 的編譯模式

Flutter 的編譯模式

使用 Flutter構建過 App的人一定有一個困惑,就是 Flutter編譯出的產物到底是什麼玩意,有時候分為幾個文件,有時候是一個動態庫,真的叫人摸不著頭腦。

本文詳細解釋一下 Flutter的編譯模式。


編譯模式的分類.

編程語言要達到可運行的目的需要經過編譯,一般地來說,編譯模式分為兩類:JIT 和 AOT。


JIT:

JIT全稱 Just In Time(即時編譯),典型的例子就是 v8,它可以即時編譯並運行 JavaScript。所以你只需要輸入源代碼字元串,v8就可以幫你編譯並運行代碼。通常來說,支持 JIT的語言一般能夠支持自省函數(eval),在運行時動態地執行代碼。

JIT模式的優勢是顯而易見的,可以動態下發和執行代碼,而不用管用戶的機器是什麼架構,為應用的用戶提供豐富而動態地內容。

但 JIT的劣勢也是顯而易見的,大量字元串的代碼很容易讓 JIT編譯器花費很多時間和內存進行編譯,給用戶帶來的直接感受就是應用啟動慢。


AOT全稱 Ahead Of Time(事前編譯),典型的例子就是 C/C++,LLVM或 GCC通過編譯並生成 C/C++的二進位代碼,然後這些二進位通過用戶安裝並取得執行許可權後才可以通過進程載入執行。

AOT的優勢也是顯而易見的,事先編譯好的二進位代碼,載入和執行的速度都會非常快。(所以編程語言速度排行榜上前列都是 AOT編譯類語言)這樣的速度可以在密集計算場景下給用戶帶來非常好的體驗,比如大型遊戲的引擎渲染和邏輯執行。

但是 AOT的劣勢也是顯而易見的,編譯需要區分用戶機器的架構,生成不同架構的二進位代碼,除了架構,二進位代碼本身也會讓用戶下載的安裝包比較大。二進位代碼一般需要取得執行許可權才可以執行,所以無法在許可權比較嚴格的系統中進行動態更新(如 iOS)。


Dart的編譯模式

Flutter使用 Dart作為編程語言,自然其編譯模式也脫離不了 Dart的干係。首先我們需要了解一下 Dart所支持的編譯模式。

Script:最普通的 JIT模式,在 PC命令行調用 dart vm執行 dart源代碼文件即是這種模式。

Script Snapshot:JIT模式,和上一個不同的是,這裡載入的是已經 token化的 dart源代碼,提前執行了上一步的 lexer步驟。

Application Snapshot:JIT模式,這種模式來源於 dart vm直接載入源碼後 dump出數據。dart vm通過這種數據啟動會更快。不過值得一提的是這種模式是區分架構的,在 x64上生成的數據不可以給 arm使用。

AOT:AOT模式,直接將 dart源碼編譯出 .S文件,然後通過彙編器生成對應架構的代碼。

總結一下剛才的列表,可以發現:

Flutter的編譯模式

Flutter 完全採用了 Dart,按道理來說編譯模式一致才是,但是事實並不是這樣。由於 Android和 iOS平台的生態差異,Flutter也衍生除了非常豐富的編譯模式。

Script:同 Dart Script模式一致,雖然 Flutter支持,但暫未看到使用,畢竟影響啟動速度。

Script Snapshot:同 Dart Script Snapshot一致,同樣支持但未使用,Flutter有大量的視圖渲染邏輯,純 JIT模式影響執行速度。

Kernel Snapshot:Dart的 bytecode 模式,與 Application Snapshot不同,bytecode模式是不區分架構的。 Kernel Snapshot在 Flutter項目內也叫Core Snapshot。bytecode模式可以歸類為 AOT編譯。

Core JIT:Dart的一種二進位模式,將指令代碼和 heap數據打包成文件,然後在 vm和 isolate啟動時載入,直接標記內存可執行,可以說這是一種 AOT模式。Core JIT也被叫做AOTBlob

AOT Assembly: 即 Dart的 AOT模式。直接生成彙編源代碼文件,由各平台自行彙編。

可以看出來,Flutter將 Dart的編譯模式複雜化了,多了不少概念,要一下敘述清楚是比較困難的,所以我們著重從 Flutter應用開發的各個階段來解讀。


在開發階段,我們需要 Flutter的 Hot Reload和 Hot Restart功能,方便 UI快速成型。同時,框架層也需要比較高的性能來進行視圖渲染展現。因此開發模式下,Flutter使用了 Kernel Snapshot模式編譯。

在打包產物中,你將發現幾樣東西:

isolate_snapshot_data:用於加速 isolate啟動,業務無關代碼,固定,僅和 flutter engine版本有關

platform.dill:和 dart vm相關的 kernel代碼,僅和 dart版本以及 engine編譯版本有關。固定,業務無關代碼。

vm_snapshot_data: 用於加速 dart vm啟動的產物,業務無關代碼,僅和 flutter engine版本有關

kernel_blob.bin:業務代碼產物


在生產階段,應用需要的是非常快的速度,所以 Android和 iOS target毫無意外地都選擇了 AOT打包。不過由於平台特性不同,打包模式也是天壤之別。

首先我們很容易認識到 iOS平台上做法的原因:App Store審核條例不允許動態下發可執行二進位代碼。

所以在 iOS上,除了 JavaScript,其他語言運行時的實現都選擇了 AOT。(比如 OpenJDK在 iOS實現就是 AOT)

在 Android上,Flutter的做法有點意思:支持了兩種不同的路子。

Core JIT的打包產物有 4個:isolate_snapshot_data, vm_snapshot_data, isolate_snapshot_instr, vm_snapshot_instr. 我們不認識的產物只有 2個:isolate_snapshot_instr和 vm_snapshot_instr,其實它倆代表著 vm和 isolate啟動後所承載的指令等數據,在載入後,直接將該塊內存執行即可。

Android的AOT Assembly打包方式很容易讓人想到需要支持多架構,無疑增大了代碼包,並且該處代碼需要從 JNI調用,遠不如 Core JIT的 Java API方便。所以 Android上默認使用 Core JIT打包,而不是 AOT Assembly。


Flutter Engine對編譯模式的支持

在我的上篇文章:Flutter原理簡解中提到,engine承載了 dart運行時,毫無疑問 engine需要和打包出來的代碼對的上號才行。

在 engine的編譯模式中,Flutter是這樣選擇的:

所以我們可以看到,Flutter的編譯模式是完全根據 Engine的支持度來設計的。


結論

看到這裡,我們完全可以得出一個結論:Flutter是一種高性能的、可跨平台的動態化應用開發方案。

在 iOS和 Android平台上,動態化完全可由 Kernel Snapshot打包實現,並且產物是一致通用的。不過目前通過打包工具進行了閹割,只能生成 debug產物。

並且如果不需要動態化,同樣可以打包出擁有更高執行性能的二進位庫文件使用。這個特性目前就已經支持

有了理論的支持,我們就可以著手做改造的事了。

如果覺得本文有幫助,不如請喝杯咖啡:

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 stephenw 的精彩文章:

TAG:stephenw |