前言
11 月 23 日,位元組跳動技術沙龍【Flutter】技術專場 在北京後山藝術空間圓滿結束。這次活動邀請到位元組跳動移動平臺部 Flutter 架構師袁輝輝,Google Flutter 團隊工程師 Justin McCandless,位元組跳動移動平臺部 Flutter 資深工程師李夢雲,以及阿里巴巴高階技術專家王樹彬和大家進行Flutter乾貨分享交流。
本文先來介紹由位元組跳動移動平臺部 Flutter 架構師袁輝輝 帶來的分享,它的主題內容是:《Flutter如何縮減接近50%的包體積》。去掉聽不懂的專業術語,按邏輯順序逐個梳理,選取最核心的部分進行解讀,主要讓大家體會視訊裡面的精華內容,學有所得。
演講作者負責 Flutter Engine 和 Dart Runtime 這兩個底層方向上的一些工作。所以這篇演講還是很有深度的。反正主要介紹了Flutter Engine 和 Dart Runtime 如何刪減,如何優化,最終把Flutter的包體積壓縮到到儘可能的小,能有多小就有多小,PPT上面是一大堆C 或C++底層引擎有關的專業術語。可能一般的小夥伴都聽得雲裡霧裡。我只能用下面這個表情包表達他們的心情:
包體積現狀包體積每上升 6MB 就會帶來下載轉化率降低 1%,當包體積增大到 100MB 時就會有斷崖式的下跌。這是Google 2016 年公佈的研究報告。隨著專案的發展,包體積會越來越大,但是隨著包體積的增大,使用者下載意願就就慢慢降低,包體積越大,使用者下載意願就越低,所以包體積優化必須提上日程。
引入Flutter前,使用OC寫,呈現出一個線性增長關係。引入Flutter後對iOS端的包體積的增長的影響,初始增長速度極快,隨著程式碼增多,增長速度逐漸減緩,最終趨近線性增長。
針對每一個環節具體優化細節下面針對每一個環節優化細節做一個詳細的說明。
Dart 編譯產物優化Flutter Release產物分析
為了說明包體積優化,以下是一個覆蓋一些業務功能點的Demo打包後的產物分析圖:
整個APP由APP Framework和 Flutter Framework兩部分組成。
● APP Framework(可變化的):
● App:Dart程式碼AOT編譯產物,它是動態連結庫
● flutter_assets: Flutter 靜態資原始檔夾,包括圖片,字型等
● Flutter Framework(固定值):
● Flutter: Flutter Engine,它是動態連結庫
● icudll.dat:國際化支援相關資料檔案
包體積優化方法
簡單地說就是3個字:刪、縮、挪。如下圖所示:
※ 刪:刪除無用程式碼,無用資源。可以手動刪除,可以機器刪除,也可以編譯時刪除。Flutter 有一個Tree Shaking 機制,從 Main 方法開始,逐級引用,最終沒有被引用的程式碼,諸如類和函式都會被裁剪掉。這個就是編譯時自動刪除。
※ 縮:比如壓縮圖片資源。
※ 挪:從專案工程或Packages裡直接挪到遠端,典型是遠端下發外掛或者安卓裡的 App Bundle,雖然“挪”對效能來說是有損的(需要動態下發),但是包體積卻大大減少。
如何“挪”?
如何“挪”的步驟如下圖所示:
移除非必要產物,動態下發:將 Dart 的編譯產物(App)分成兩部分:Part1 和 Part2,然後把 Part2 挪出去;然後把 flutter_assets 這個資料夾和icudtl.dat 挪出去。最後包體積就非常小了。
Dart原始碼編譯流程(略)
【問】:關於“挪”的那部分,說明一下:為什麼Android可以全部挪動 Dart 的編譯產物(App),iOS卻不可以?
【答】:載入到記憶體後,指令段需要賦予可執行許可權,iOS無法隨意標記記憶體可執行,instr段必須在動態庫內隨包下發。如下圖所示:
動態下發方案(重點學習)
挪動過程如下圖所示:
Flutter 工程編譯安卓的包,會編譯成 4 個snapshot檔案,分別是:
※ isolate_snapshot_instr
※ isolate_snapshot_data
※ vm_snapshot_instr
※ vm_snapshot_data
這裡用動態下發模式的示例來舉例說明,把isolate_snapshot_data和vm_snapshot_data移出來,flutter_assets也移出來。挪動前:9.2M,挪動後3.8M,如果App體積龐大,那麼這個收益會更明顯。
【問題1】:關於iOS32位和64位雙架構問題,不可能內建兩份 Zip 包。
解決思路:將引擎 Zip 包置於 APP 動態庫內,App Store 可以針對動態庫自動實現分架構下發,如果不知道怎麼做,可以參考:Dart 的Observatory Server 的 Web 靜態資源,它是整個直接打到 Dart 的執行時裡的,這麼好的案例,當然可以參考一下。
【問題2】:風險應對。比如:下載失敗,解壓失敗怎麼辦?
Flutter 引擎編譯產物優化接下來是Flutter.framework這一塊的優化。如圖8所示:
統一編譯引數
在 Flutter 引擎編譯時,安卓和 iOS 的編譯引數不同,安卓是-OZ,iOS 是-OS,想追求極致包體積是需要用 OZ 的,不能用 OS。解決:只需要升級最新的 build-tools,改 OS 為 OZ。
定製化編譯
這塊結合各個廠商、各個 APP 可能不一樣,有幾點可以借鑑的:
(1)移除 boringSSL,可用 Method Channel 呼叫源生網路庫來替代 Dart Http 功能,這樣效能絕對有提升,同時還能帶來包體積的收益。
(2)定製化編譯skia,skia裡面的引數很多,其中有 3 個位元組跳動團隊已經試過了,去掉之後最終得到收益不到 200KB。官方有更高階的概念叫模組化編譯,核心思想是把 Engine 拆成不同的Modular,根據自己的情況定製化編譯。
機器碼指令優化最後一部分是最高階最偏向於底層的了,看到大佬這般騷操作,簡直是慚鳧企鶴、望洋興嘆、望塵莫及、高不可攀、難以望其項背、不可同日而語,各位對演講嘉賓嘖嘖稱羨,簡直有一種外慕徙業的想法,但是終究是方案,沒看到程式碼,最後只能是對屠門而大嚼。
機器碼指令優化如圖9所示:
實驗證明:OC 出來的機器碼就比 Dart 厲害一點。比如一個簡單是示例,一個函式返回自定義的 View,函式被copy次數,直接影響了包體積大小,Dart和OC編譯出來的包體積和其函式被copy次數成線性關係。其中Dart 的斜率遠高於 OC。
比如做過一個簡單的函式測試,反編譯發現,使用OC寫的程式碼,生成了 11 條彙編指令,但是Dart生成了32條彙編指令,Dart 的彙編指令明顯比OC多了很多。這也就說明了,為什麼Dart和OC生成的包體積斜率不一樣的原因。
【解決方案】:所有函式前 8 個都有指令對齊頭,後 6 位都有對齊指令,一頭一尾可以移除。中間還有 18 條指令,然後這18條指令中,有 5 條是為了做棧溢位檢查(可以考慮移除),OC 沒有這個指令,還剩 13 條必要指令,基本與 OC 11 條持平,也存在優化空間。請看下圖:
谷歌也在針對這個做優化,有一些已經落地,有些還在推進中。
提問環節最後就是觀眾提問環節:
Q:谷歌的引擎是一直在迭代的,如果我從現代的版本開始修改引擎,以後谷歌的引擎更新了,我要不要馬上跟進?
A:這個問題很多人都問過我們,在 Flutter 團隊引擎不停迭代時,你的自定義引擎如何跟上節奏?這個問題是我們不需要緊跟潮流,我們挑一個穩定版本,154 做有針對性的優化,過兩個月再判斷一下,比如現在 191 適合不適合做適配、做遷移,如果適合,那我們就做,如果不適合,或者業務方沒有緊急需求,那就不升。為什麼有些團隊升到 178?因為海外要支援雙架構,原來的不夠,只能支援單架構,那麼 178 預設模式就改成 Library Mode,支援 32 位和 64 位,是為了這個事情。如果你沒有這個強烈的需求,反而用自己公司內部的引擎更穩定一點。
Q:我是一名移動端研發。我們通過“挪”的方式使包體積變小了,但是使用者在使用實際模組當中又要挪回來,我想問的是使用者在使用某個模組時,把我們挪出去的這部分挪回來的時候,這個轉場我們應該怎麼去處理?或者有什麼更好的方案讓使用者無感知的載入我們挪出去的這部分東西?
A:還是跟剛才的問題一樣。這個問題對於位元組跳動的 Android 研發來講還挺司空見慣的,一個功能挪出去以後,構成外掛以後,也會面臨你這個相同的問題。這很簡單,就判斷一下外掛是否存在,iOS 也要判斷引擎是否存在,要麼是否展示介面、是否展示功能入口。你如果一定要在啟動階段首頁展示這個功能的話,那你就只能阻塞一下了。
Q:您提到有一個位元組碼優化的問題,剛才講到 Dart 語言應該是執行在虛擬機器上的,這個位元組碼優化是優化編譯的中間語言?還是由 Dart 虛擬機器最終生成?
A:不,它是機器,在 Release 模式下執行的是機器碼。在編譯器由 Dart 虛擬機器生成的,但是實際執行的時候它是一個完完全全的機器碼。
Q:關於剛才提到位元組碼指令的問題,Dart 針對你舉的例子裡,同一條 OC 是 11 條,Dart 是 32 條,這種情況對於我們來說,我們自己沒辦法做這個優化,但是實際過程中要不要儘量減少小函式,這樣是不是也是一種方式?
A:實際使用中應該不會寫到像我今天展示這麼小的函式,應該不會直接打出一個 Int,實際使用的函式遠比這個要複雜,在實際使用過程中 Dart 的程式碼和 OC 的區別沒有今天展示的那麼大,我只是為了演示冗餘指令,專門挑了一個特別對 Dart 不友好的 demo,但實際上沒有那麼大的區別。如果已經用上動態下發模式的話,留在包裡的指令段真的是非常少的一部分,我們只是追求極致把事情做到盡善盡美,但是這部分就算不優化也應該是可以接受的,我們線上已經在跑著、已經在廣泛用 Flutter 了,這應該不是阻礙你釋出或者採用它這個技術棧的原因。
在這裡我分享一份私貨,自己收錄整理的Android學習PDF+架構視訊+面試文件+原始碼筆記,還有高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習
如果你有需要的話,可以點贊+評論+轉發,關注我,然後私信我【進階】我發給你