Flutter版本為: 1.9.1
利用battery的example作為實驗
Flutter的包體積一直是個比較大的問題,感謝位元組跳動在包體積裁剪這塊的分享,讓我們有了一些方向,該文章就其中一個方案資料段壓縮做了詳細分析和實踐。
battery的example工程在經過資料區域壓縮後,App動態庫由10.5MB減小到了7.9MB。當然所寫的程式碼越多能減少的體積也會變多。和其它移除某些功能模組的方案相比,該方案我認為是收益最大的。
基礎知識介紹
產物
Flutter.framework:編譯過程中直接從Flutter環境中拷貝而來
- Flutter:Flutter引擎,Mach-O格式的動態連結庫
App.framework: 編譯工程時生成
- App:AOT Snapshot資料,由我們的Dart程式碼編譯而成,Mach-O格式的動態連結庫
這兩個動態連結庫都會在應用啟動時,因為被最外層的Runner所使用而被載入進記憶體,可以通過otool檢視Runner和它們的聯絡
Dart執行方式
Dart VM有三種執行方式
- 直接JIT執行原始碼或者Kernel binary (app.dill)
- 執行生成的JITSnapshot,和第一種方式相比利用快照減少了JIT預熱時間
- 執行生成的AOTSnapshot,直接執行編譯期編譯好的機器碼
iOS採用的是AOTSnapshot的方式,雖然JITSnapshot因為執行時會進行一些優化,當達到完全預熱時,效能能達到最高。但是需要在引擎中引入即時編譯器增加引擎體積。
Dart執行AOTSnapshot
Dart 虛擬機器將已存在記憶體中的isolate的堆(駐留在堆上的物件圖)序列化成二進位制的快照檔案,當在裝置上再次啟動虛擬機器的時候可以從快照中快速重建形同的isolate狀態。實質上由圖可知是一個序列化和一個反序列化的過程。
Dart 虛擬機器的快照和其它快照有些不同,是包含機器碼的,當這塊機器碼是不需要反序列化的,因為放在程式碼區,對映到記憶體的時候可以直接成為堆的一部分。
App這裡面只包含了4個符號
- _kDartIsolateSnapshotData
- _kDartIsolateSnapshotInstructions
- _kDartVmSnapshotData
- _kDartVmSnapshotInstructions
R 表示該符號位於只讀資料區
T 表示該符號位於程式碼區
分析與實踐
寫入時壓縮
Dart原始碼編譯成App.framework的流程如下
bin/cache/artifacts/engine/ios-release/gen_snapshot_armv7 --causal_async_stacks --deterministic --snapshot_kind=app-aot-assembly --assembly=build/aot/armv7/snapshot_assembly.S --no-sim-use-hardfp --no-use-integer-division build/aot/app.dill
讀取時解壓
SearchMapping最後也是呼叫dlopen與dlsym去獲取符號內容,所以在實現讀取解壓時,只需要在ResolveVMData和ResolveIsolateData中的合適的位置做解壓縮的操作,並利用解壓資料替換解壓前的資料即可。
總結
更新的flutter版本還未嘗試,應該變化也不大,這個方案也可以繼續用。
參考
Introduction to Dart VM: mrale.ph/dartvm/
如何縮減接近 50% 的 Flutter 包體積 | Flutter 沙龍回顧:juejin.im/post/5de8a3…
Flutter機器碼生成gen_snapshot:gityuan.com/2019/09/21/…