Flutter包體積之資料區域壓縮分析與實踐

zfc發表於2020-04-18
Flutter版本為: 1.9.1
利用battery的example作為實驗

Flutter的包體積一直是個比較大的問題,感謝位元組跳動在包體積裁剪這塊的分享,讓我們有了一些方向,該文章就其中一個方案資料段壓縮做了詳細分析和實踐。

battery的example工程在經過資料區域壓縮後,App動態庫由10.5MB減小到了7.9MB。當然所寫的程式碼越多能減少的體積也會變多。和其它移除某些功能模組的方案相比,該方案我認為是收益最大的。

基礎知識介紹

產物

當在iOS工程引入了Flutter之後,產物中將新增兩個Framework,App.framework和Flutter.framework。

Flutter包體積之資料區域壓縮分析與實踐

Flutter.framework:編譯過程中直接從Flutter環境中拷貝而來

  • Flutter:Flutter引擎,Mach-O格式的動態連結庫

App.framework:  編譯工程時生成

  • App:AOT Snapshot資料,由我們的Dart程式碼編譯而成,Mach-O格式的動態連結庫

這兩個動態連結庫都會在應用啟動時,因為被最外層的Runner所使用而被載入進記憶體,可以通過otool檢視Runner和它們的聯絡

Flutter包體積之資料區域壓縮分析與實踐

Dart執行方式

Dart VM有三種執行方式

  1. 直接JIT執行原始碼或者Kernel binary (app.dill)
  2. 執行生成的JITSnapshot,和第一種方式相比利用快照減少了JIT預熱時間
  3. 執行生成的AOTSnapshot,直接執行編譯期編譯好的機器碼

iOS採用的是AOTSnapshot的方式,雖然JITSnapshot因為執行時會進行一些優化,當達到完全預熱時,效能能達到最高。但是需要在引擎中引入即時編譯器增加引擎體積。

Dart執行AOTSnapshot

Flutter包體積之資料區域壓縮分析與實踐

Dart 虛擬機器將已存在記憶體中的isolate的堆(駐留在堆上的物件圖)序列化成二進位制的快照檔案,當在裝置上再次啟動虛擬機器的時候可以從快照中快速重建形同的isolate狀態。實質上由圖可知是一個序列化和一個反序列化的過程。

Dart 虛擬機器的快照和其它快照有些不同,是包含機器碼的,當這塊機器碼是不需要反序列化的,因為放在程式碼區,對映到記憶體的時候可以直接成為堆的一部分。

使用nm指令檢視App符號可以看到兩個架構的4個符號

Flutter包體積之資料區域壓縮分析與實踐

App這裡面只包含了4個符號

  • _kDartIsolateSnapshotData
  • _kDartIsolateSnapshotInstructions
  • _kDartVmSnapshotData
  • _kDartVmSnapshotInstructions

表示該符號位於只讀資料區

表示該符號位於程式碼區

所以我們可以進行壓縮處理的資料即kDartIsolateSnapshotDatakDartVmSnapshotData。在生成Snapshot的時候,在序列化後先壓縮再寫入,在執行時先解壓再反序列化。

分析與實踐

寫入時壓縮

Dart原始碼編譯成App.framework的流程如下

Flutter包體積之資料區域壓縮分析與實踐

要實現在寫入快照時針對性壓縮,需要在第二步中進行處理,也就是在gen_snapshot裡面處理,這裡呼叫的指令為

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

根據dart原始碼gen_snapshot呼叫流程圖如下

Flutter包體積之資料區域壓縮分析與實踐

真實寫入到彙編的位置在image_snapshot.cc檔案的AssemblyImageWriter::WriteText中,只需要針對data資料寫入時做個壓縮即可。flutter引擎其中包含了zlib模組,所以在這裡的處理可以利用zlib完成,這樣也不會增加額外的體積。為了後續解壓,在壓縮資料時需要記錄下壓縮前的大小,解壓時方便分配合適記憶體。

讀取時解壓

讀取的邏輯在flutter的框架中,這裡呼叫圖直接從DartVMData::Create開始

Flutter包體積之資料區域壓縮分析與實踐

這四個呼叫SearchMapping就是去獲取對應App.framework/App裡面的四個符號內容。往下繼續跟蹤SearchMapping

Flutter包體積之資料區域壓縮分析與實踐

SearchMapping最後也是呼叫dlopendlsym去獲取符號內容,所以在實現讀取解壓時,只需要在ResolveVMDataResolveIsolateData中的合適的位置做解壓縮的操作,並利用解壓資料替換解壓前的資料即可。

總結

本文對Flutter的資料段壓縮主要是針對iOS進行分析,因為Flutter帶來的包體積對iOS影響更加嚴重,但是如果想要對Androidlibapp.so進行中的data資料段壓縮也是完全可以的。

更新的flutter版本還未嘗試,應該變化也不大,這個方案也可以繼續用。

參考

Introduction to Dart VM: mrale.ph/dartvm/

如何縮減接近 50% 的 Flutter 包體積 | Flutter 沙龍回顧juejin.im/post/5de8a3…

Flutter機器碼生成gen_snapshot:gityuan.com/2019/09/21/…


相關文章