Android平臺上的秒級編譯方案
Freeline是什麼?
Freeline是螞蟻聚寶團隊15年10月在Android平臺上的量身定做的一個基於動態替換的編譯方案,5月阿里集團內部開源,穩定性方面:完善的基線對齊,程序級別異常隔離機制。效能方面:內部採用了類似Facebook的開源工具buck的多工程多工併發思想:埠掃描,程式碼掃描,併發編譯,併發dx,併發merge dex等策略,在多核機器上有明顯加速效果,另外在class及dex,resources層面作了相應快取策略,做到真正增量開發,另外引入並優化buck的部分加速元件dx,DexMerger,資源編譯方面,深入改造了Aapt資源編譯流程,當資源發生改變時候,秒級完成增量包編譯,其中增量包僅含最小的變更集合(10Kb~數百Kb內),後期也被運用到線上進行資源/程式碼動態替換。相比目前instant-run,buck,layoutcast等方案快數倍速度。有何優勢?
1.真增量,構建過程快且增量包體積小,極大提升更改程式碼部署到手機速度,較Android studio2.0及 LayoutCast快3~5倍
2.跨平臺Linux,mac,windows
3.全版本覆蓋 2.x ~ 6.x版本均支援
4.部署流程簡化,更改程式碼後,構建過程中,與手機建立了tcp長連線,一行命令即可完成增量部署,毋需到各自子bundle所在的目錄構建完成後再進入portal/launcher進行打包再安裝到手機的過程
5.事務支援,在開發過程引入的異常不會破壞工作空間
6.無縫支援mPass,解決了類似maven各個節點需merge合併等與常規開發流程不一致的問題
7.程序級別異常隔離,開發體驗持續穩定
誰在用?
目前 Freeline 穩定支撐聚寶ANDROID技術團隊日常開發,由於相容mPaas/gradle架構
方案對比
先看看傳統的Android打包流程:
單執行緒沿著流水式的任務從上到下進行打包構建,其中,aapt會執行2次,第一次是生成R.java,參與javac編譯,第二次是對res裡面的資原始檔進行編譯,最後APKBuilder會把DEX檔案與編譯好的資原始檔及DEX檔案進行打包成APK,簽名並安裝至手機。整個流程下來,沒有任何快取,沒有併發,也沒有增量,每次構建都是一個全新的過程,所以每次構建時間也比較恆定,程式碼量,資源量越多,構建時間越慢。
下面對業內3個比較主流的增量構建方案進行對比分析:
LayoutCast:
增量的思想源自於LayoutCast,與LayoutCast不同的是,Freeline把連線裝置與各個工程間掃描及構建增量包任務仿照Buck的思路進行拆解,而LayoutCast是傳統流水式任務構建,效能可能會被中間某個環節的耗時被拖慢,沒有充分利用到多核優勢。另外在資源變化後,LayoutCast選用的方案與Android Studio2.0 instant-run思路一致 ,把整個應用的資源打成資源包,推送至手機,若資源包大小較大,該過程耗費的時間相當可觀,而資源包的大小直接影響了後面通過tcp傳輸的耗時,從資源變更的角度的來說,LayoutCast並實現沒有真正意義上的增量。
另外,LayoutCast通是過執行期手機端反射R Class field的方式來生成ids.xml及public.xml,用於保證增量包的資源id與全量包的資源id一致,該方案存在幾個缺陷:
1.通過手機端執行期反射,R有上萬個field,效率相當低下,Galaxy note4上耗費近1s
2.存在致命缺陷,舉個例子,app宣告的attrs.xml若存在一個定義如下:
attr name="ptrMode"> <flag name="pullFromStart" value="0x1" /> <flag name="pullFromEnd" value="0x2" /> </attr> 其中 ”pullFromStart“,”pullFromEnd” 對應的id值如下: public static final class id { public static final int pullFromEnd=0x56050005; public static final int pullFromStart=0x56050004; }
實際上上面2個列舉常量生成的id的type型別是“id”,若生成ids.xml及public.xml時候,不排除這些列舉id,最終的結果就是aapt給每個資源分配id時候,發生陣列越界,aapt程式coredump掉,無法構建出資源包,而手機端執行期反射時候,僅知道”pullFromStart“,”pullFromEnd”為id型別,不足以知道其對應的是列舉常量,故僅僅通過執行期反射,上述致命缺陷無法解決。
再有,由於沒有快取機制,LayoutCast編譯速度會隨著修改檔案的增加越來越慢。最後,由於程式碼增量使用的是dex插入系統dexlist最前位置的方式,在4.x的機器上面系統安全校驗不通過,所以LayoutCast並不支援5.0以下的手機。
BUCK
下面說說BUCK,先看一幅其官方的構建過程圖
BUCK把原來單流水線任務以工程為單位拆分成多個可併發執行的子任務節點,梳理好各個節點前後的依賴關係,整理出有向拓撲圖,通過多條執行緒併發把各個子任務節點構建出來,充分利用多核優勢。
BUCK建立了一套完善的依賴規則以及細化的快取系統來縮減編譯時間,其增量構建的原理,實際是以工程目錄為單位進行增量構建,發生變更時候,變更的工程,以及該工程作為父節點或祖先節點的工程,均需要重新構建,構建完這些變更涉及的工程後,Buck需要重新走一次合併各工程DEX,對齊,簽名,打包APK的過程,構建完畢後,還要繼續走安裝流程,到最後手機檢視修改效果時,可能還需要幾個頁面的切換才能進入之前修改的頁面,這些流程整個下來,耗費的時間是相當可觀的,另外不支援windows,以及較強的入侵性(整個工程需要做較大的調整才能使用)均是接入BUCK的門檻,但不得不承認,若作為全量構建的角度,BUCK的確是不二的選擇,背後還有強大的Facebook技術團隊在維護,在Facebook內部,所有的app構建工具均為BUCK,在國內,BUCK也被微信應用為預設構建方案。
instant-run
最後是谷歌官方的增量解決方案Android Studio2.0 instant-run ,首先其基本流程與LayoutCast有點相似,但因其程式碼增量是通過執行期hack method實現,所以進行了instant-run後,實際App沒有重新走原有該走的生命週期,導致要看到類似onCreate,onResume等生命週期方法修改後的效果,必須手動重啟一次程序,另外因為不同手機指令集合的不同,instant-run還會有一定掛掉的機會,最後,因為instant-run採用hack的方式,導致debug包除錯時候無法看到對應的method堆疊,不得不說,這是個巨大的弊端,最後,與LayoutCast一樣,instant-run不支援5.0以下的機器。
核心思想
正因為上面幾個方案各自有各自的優缺點,Freeline融合各自優點而生,核心技術思想源自於Buck,LayoutCast,並在此基礎上進行一步改良,爭取把增量思想做到極致。
主要有如下幾點:
多工併發,多級快取,增量範圍最小化,懶載入,基於長連結無安裝式執行期動態替換,基線對齊觸發機制,可除錯
多工併發
研究過Buck的同學應該清楚,Buck把原來單流水線任務以工程為單位拆分成多個可併發執行的子任務節點,梳理好各個節點前後的依賴關係,整理出有向拓撲圖,通過多條執行緒併發把各個子任務節點構建出來,充分利用多核優勢,在macbook上預設16條執行緒併發。
Freeline在啟動時候仿照了Buck,根據工程間及任務間的依賴關係,提前計算好有向拓撲圖,進行併發任務執行,預設開啟8條執行緒(因聚寶工程數較少,沒有必要開啟過多執行緒),下面先簡單介紹一下相關知識:
有向拓撲圖:
拓撲圖是圖的一種,“有向”保證了依賴關係和順序關係,可以有多個根,
子可以有多個父
下面先以一張圖簡要說明Freeline構建期間各個工程任務工作次序:
整個工程角度來看,主要分成:
PC端與手機建立TCP長連線,掃描各個子工程檔案變化,各個子工程的增量dex構建,增量資源包構建,合併所有工程dex,傳輸增量包
上圖中,分叉的箭頭代表任務是併發的,同一時間,不同的工程可能處於不同的構建階段,Freeline在啟動時候,會先定義好各個子工程及其子任務前後的依賴關係,每個任務的前置任務,後置任務,位於同一層級的工程會進行併發構建,預設8執行緒併發。
單個工程流程:
上面從總體上介紹了Freeline工作的整體情況,接下來詳細介紹Freeline每個子工程做的事情:先以一張圖說明Freeline構建期間單個工程為單位的任務流程: