Flutter 包體積之數據區域壓縮分析與實踐

前言

文章經過作者同意,轉發到本博客:Flutter包體積之數據區域壓縮分析與實踐

  • 背景:在存量iOS應用中,接入Flutter框架,即混合開發模式,大概會給存量的iOS應用增加10M的包體積。本篇文章介紹Flutter包體積優化的一種思路。

大綱

  • 開發環境:Flutter 1.9.1
  • 實驗工程:官方維護的Flutter插件battery_example
  • 實驗效果:App動態庫由10.5MB減小到了7.9MB。

Flutter的包體積一直是個比較大的問題,感謝字節跳動在包體積裁剪這塊的分享,讓我們有了一些方向,該文章就其中一個方案數據段壓縮做了詳細分析和實踐。battery的example工程在經過數據區域壓縮後,App動態庫由10.5MB減小到了7.9MB。當然所寫的代碼越多能減少的體積也會變多。和其它移除某些功能模塊的方案相比,該方案我認爲是收益最大的。

基礎知識介紹

產物

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

在這裏插入圖片描述

  • Flutter.framework:編譯過程中直接從Flutter SDK中拷貝而來。
    • Flutter:Flutter引擎,Mach-O格式的動態鏈接庫。
  • App.framework: 編譯工程時生成
    • App:AOT Snapshot數據,由我們的Dart代碼編譯而成。Mach-O格式的動態鏈接庫這兩個動態鏈接庫都會在應用啓動時,因爲被最外層的Runner所使用而被加載進內存,可以通過otool -l Runner查看Runner和它們的聯繫。

在這裏插入圖片描述

Dart運行方式

該章節內容來源於:Introduction to Dart VM

Dart VM有三種運行方式

  1. 直接JIT運行源碼或者Kernel Binary,最終產物形式爲app.dill;
  2. 運行生成的JITSnapshot,和第一種方式相比利用快照減少了JIT預熱時間運行生成的
  3. AOTSnapshot,直接運行編譯期編譯好的機器碼。

iOS採用的是AOTSnapshot的方式。雖然JITSnapshot模式在運行時會逐步完成預熱,當JITSnapshot達到完全預熱時,性能也將達到最高。但是JITSnapshot運行模式需要在引擎中引入即時編譯器,會增加引擎大小。

Dart運行AOTSnapshot

在這裏插入圖片描述在編譯期間,Dart 虛擬機將已存在內存中的isolate的堆(駐留在堆上的對象圖)序列化成二進制的快照文件,當在設備上再次啓動虛擬機的時候可以從快照中快速重建isolate的狀態。本質上是一個序列化和一個反序列化的過程。

Dart 虛擬機的快照和其它快照有些不同,是包含機器碼的,當這塊機器碼是不需要反序列化的,因爲放在代碼區,映射到內存的時候可以直接成爲堆的一部分。

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

在這裏插入圖片描述
從上圖可知:App.framework中只包含了4個符號。

符號 說明
_kDartIsolateSnapshotData Dart Isolate數據段
_kDartIsolateSnapshotInstructions Dart Isolate指令段
_kDartVmSnapshotData Dart虛擬機數據段
_kDartVmSnapshotInstructions Dart虛擬機指令段

說明:

  • R 表示該符號位於只讀數據區
  • T 表示該符號位於代碼區

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

分析與實踐

寫入時壓縮

Dart源碼編譯成App.framework的流程如下:
在這裏插入圖片描述
要實現在寫入快照時針對性壓縮,需要在第二步中進行處理,也就是在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調用流程圖如下:

在這裏插入圖片描述
真實寫入到彙編的位置在image_snapshot.cc文件的AssemblyImageWriter::WriteText中,只需要針對data數據寫入時做個壓縮即可。flutter引擎其中包含了zlib模塊,所以在這裏的處理可以利用zlib完成,這樣也不會增加額外的體積。爲了後續解壓,在壓縮數據時需要記錄下壓縮前的大小,解壓時方便分配合適內存。

讀取時解壓

讀取的邏輯在flutter的框架中,這裏調用圖直接從DartVMData::Create開始:

在這裏插入圖片描述
這四個調用SearchMapping就是去獲取對應App.framework/App裏面的四個符號內容。往下繼續跟蹤SearchMapping:

在這裏插入圖片描述
SearchMapping最後也是調用dlopen與dlsym去獲取符號內容,所以在實現讀取解壓時,只需要在ResolveVMData和ResolveIsolateData中的合適的位置做解壓縮的操作,並利用解壓數據替換解壓前的數據即可。

總結

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

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

附錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章