在”阿裡開源項目最佳實踐“上,螞蟻金服用戶端開發工程師黃詠分享了freeline整個的開源曆程和變化,他從不同的角度講述了freeline整個技術底層的原理,以及編譯加速方案的對比,并分享了freeline整個開源以來的收獲和體會。
以下内容根據現場分享和幻燈片整理而成。
freeline是非常快速的編譯工具,其誕生主要為了迎合現在工程的需要,實作更好的動态化。freeline最早誕生之初主要是為了支援螞蟻聚寶的應用架構(mpaas,插件化架構)的增量編譯,它于2016年8月在alibaba github上開源,目前為止已經累計了3244個star,目前是alibaba github下排行前十的開源項目,已有上千款應用接入使用freeline,可能是東半球使用者最多的第三方編譯工具。
為什麼選擇freeline?
目前,市面上類似freeline的工具有很多,如google官方的instant run、facebook-buck、jrebel for android等,那麼freeline和這些工具相比究竟有什麼優勢呢?下面來一探究竟。
<b>instant run</b>
<b> </b>

首先介紹一下google官方的instant run,其優點是android studio随身攜帶,下載下傳之後,預設打開全部功能,零配置,相對其他方案最為穩定,基本無侵入性影響。instant run可以在1s或2s之内完成所有的編譯效果。它的缺點是:首先對增量編譯的支援有局限性,無法使所有的代碼修改都支援增量編譯,尤其是跨子產品修改。比如現在一些大型的android工程,不會是單個子產品,而是數十個以上,如果工程師跨越了2個或3個module來修改,同時進行編譯,一般來說instant run隻進行全量編譯,因為目前不支援這樣的修改情況;其次,修改java檔案會對整個application進行重新開機,比如說要修改一個頁面,可能是4-5個層級,進入一個較深的層級後,使用instant run就有可能使app回到最初始的頁面,又要重新花4-5個步驟,進入到深層級;另外,instant run在asm植入時,植入一段自己的代碼,當增量修改之後,無法進行debug,帶來調試困難;instant run無法支援複雜的工程結構(主要指非常複雜,十幾個子產品以上的工程),也不支援kotlin與jack。
<b>facebook–buck/uber-</b><b>o</b><b>k</b><b>b</b><b>uck</b>
<b></b>
facebook-buck是facebook出品的,其内部統一使用的建構系統。facebook整個後端的服務端工程包括ios工程或安卓工程,基本上都統一由buck作為建構系統來統一建構,但使用buck要做一些額外的系統配置,這對于幾乎沒有接觸過buck的工程師來說,門檻相對較高。okbuck是一個幫助gradle工程快速內建buck的開源工具,目前由uber維護。buck一個核心的出發點是多線程開發編譯,充分利用緩存,buck通過對一個子產品拆分,拆分成各種小module,近似達到增量編譯效果、加速效果。而且其效果非常好,一般十幾秒就可以完成編譯。并且,buck也已經支援目前非常流行的retrolambda表達式的插件。
當然,buck也有其局限性。對于有曆史包袱的大型工程接入,想要接觸buck,需要較高的成本。建構過程與gradle不同,即使是在okbuck幫助的情況下,很多gradle的東西都需要工程師做專門的适配,才能在buck上面使用,有較高的學習曲線(國内基本沒有太多相關資料),也無法迅速用上社群最新的技術。而且,buck需要重新安裝apk,在安卓7.0以前,一個30m左右的apk安裝時間甚至可以達到30s以上,雖然編譯過程可能就十幾秒,但加上整個安裝過程,包括重新進入頁面的過程,可能需要1分鐘的時間等待編譯重新開始與調試。buck不支援windows。一些國内的工程師會有所顧忌,沒有辦法迅速接入;它不支援kotlin,這也是一些使用新技術公司所擔憂的。
<b>jrebel for android</b>
jrebel是一家來自國外開發公司zeroround的開發工具,是比instant run還早的增量編譯工具。zeroround公司最早做jvm熱部署,支援spring以及ssh架構,有大量的實踐積累,做了很多優化與性能。如果要找一個instant run的替代品,jrebel是一個很好的選擇。它是一個幾乎零配置的工程,隻需要安裝一個插件,即可立刻運作,zeroround公司在這方面花了非常大的精力,支援retrolambda與大量流行的android開發元件庫。原理為位元組碼層面的動态加載,是以它理論上支援kotlin、groovy等各種基于jvm的語言。
該工具同樣有一些缺陷,最大的問題是收費,且價格不菲,不過現在可以免費試用一年。且它不支援databinding,隻有收費版才能debug,而且由于熱部署功能以及基于位元組碼層面的動态加載,導緻沒有辦法使用ide內建的debug插件,必須使用jrebel for android單獨提供的插件進行debug功能。還有,crash後需要重新全量編譯,單次全量編譯、安裝的速度非常慢。它不支援jack。
freeline
那麼freeline與這幾種工具相比,具體有哪些優勢呢?
目前來說freeline已經支援了絕大多數場景的增量編譯和java檔案的修改,這一點比instant run範圍廣。目前來說支援retrolambda與apt,部分支援databinding,因為本身開發不是使用databinding,是以隻能說在自測的範圍内已經支援了databinding絕大部分的feature。但是可能支援的不全,且它也處于持續的疊代中,這也是在後期疊代時注意的一些地方;另一個優點是支援so檔案的動态替換,尤其是一些地圖應用或出行應用,對于整個開發調試會非常有用,這也是freeline相當于目前幾個開發工具不一樣的地方;同時,freeline的增量資源是真正的增量資源,大小可以達到幾十k,甚至幾百k ,i/o很小,如果仔細看instant run打包的資源包,整個i/o傳輸會使用很長時間。比如說一個十幾m的資源包,在不同的usb環境下,因為freeline真正做到資源包達到了增量的級别,是以會使傳輸速度達到7秒或8秒,整個增量傳輸非常快。它支援windows/linux/mac,進而覆寫了更廣泛的使用者,做的比jrebel好的一點就是appcrash後,仍然能夠進行增量編譯來修複,基本上不需要重新安裝,是以,重新把代碼修改好,運作freeline插件,就可以立刻完成整個增量的修複。
freeline也存在不足的地方,因為在做适配時,更多針對複雜的、十幾個子產品以上的工程,是以對于單module工程,基本上沒有優勢,不如直接就使用instant run。其次是hack方案的本質導緻了存在無法避免的相容性問題。另外,它不支援删除帶id的資源,是以資源id删除時,可能需要重新運作freeline進行全量編譯。它也不支援kotlin與jack。
freeline的原理
freeline的本質是基于gradle建構系統上的一套hack解決方案。去年是熱替換爆發的年度,而freeline其實是熱替換方案在編譯時的實際運用,實作實時熱替換,即實作了整個增量編譯的效果。hack方案本身就存在相容性問題,從理念上,它充分利用多核性能,多級緩存,盡可能減少不必要的編譯步驟。從上圖可簡單分析freeline過程,除了主程序,還會單獨運作freeline程序,還有socket長鍊,通過freeline程序與主程序間的通信來完成整個patch過程,實作熱替換,這也是其運作模型。
<b>freeline全量編譯</b>
上圖是freeline整個全量編譯的流程。從圖中可以看出,中間有一個過程是gradle-full-build-with-freeline,freeline在整個gardle全量編譯的過程中植入hack,在hack過程中收集了module之間的依賴關系,包括依賴檔案存放在哪裡,整個過程結束之後,還會進行build-base-res過程。如果隻是看gradle的resource編譯過程,gradle會将所有的resource進行merge,這就會導緻在resource編譯的時候,沒有辦法将compile出來的resource資源進一步加工編譯,是以freeline在此基礎上,進行build-base-res,進行全量資源包的建構,為後面的增量資源做準備。通過該圖可以清楚看到相應的并發及依賴關系。
<b>freeline增量編譯</b>
接下來看freeline增量編譯過程。從右圖可以看出,中間的流水線下來之後,整個并發過程在多子產品編譯,能夠做到最小粒度的編譯單元。如果對比facebook的buck,會發現buck 的編譯,整個建構單元基于module,這就意味着如果整個工程的module沒有做好,buck不會達到速度提升十幾倍的編譯效果。是以如果不使用freeline而使用buck,就會對整個工程的架構模式進行深度拆分,拆分成多個比較細小的子產品,freeline盡可能dex merge。dx過程非常耗時,gradle在原本的編譯過程中,所有的module編譯跟class編譯是合并的過程,為了進行更好的加速效果,省掉了很多不必要的步驟,将所有的資源編譯統一,單獨做一個統一的增量資源編譯。像整個databinding,基于新的增量過程,生成新的java檔案。在資源編譯過程中,動态修改整個r檔案,讓新增的資源id能夠進入最新的id,傳遞給後面編譯過程中的java module,之後再做一個merge-dex,來實作增量編譯,最終完成sync同步,實作資源編譯。
整個過程中也有一些缺陷,如果依靠全量生成classmap,就沒有辦法在增量過程中實作。因為在增量過程中,拿不到全量的class依賴,是以這是freeline不能支援的一個地方。如果要讓路由元件支援freeline編譯,需要對路由資源進行單獨的拓展開發,讓freeline作為調用,才能實作整個freeline的拓展實作。
未來規劃
接下來談一下freeline未來的規劃進展。首先關注android gradle plugin的最新動态:
去年google推出了android gradle plugin2.2.0版本時說,如果将之前版本中的instant run關閉的話,那這個版本的instant run是值得打開的。因為這個版本對其做了非常大的優化和改進,包括對多module工程的支援,得到了比較高的提升,但編譯速度不是特别理想。從上表的對比可以看出,針對于100個module的工程進行的測試,2.2.0版本在configuration階段接近2min多,僅僅是改變一個java檔案也需要這麼久,如果使用freeline沒有那麼大的耗時。因為如果沒有工程的變更,configuration等過程可能就全部跳過了。google官方推出的2.3版本,能夠縮小到9s,2.5版本裡面号稱可以做到2.5s的configuration時間,而且是6.4s之内完成一行java檔案的變更,立刻生效,這是非常大的性能改進,是以這也是freeline未來繼續跟進關注的技術進展,來看是否可以回饋到freeline的開發過程當中,實作更好的開發過程。
freeline同時也會跟進google的aapt2的進展。整個看lsp源碼會發現,google很多源碼其實是采用了aapt2編譯,aapt2真正做到了類似于c++中的子產品化編譯,單元編譯,它将每個module自帶的資源編譯成一個單獨的resource包,最終将所有編譯出來的resource包通過一個link指令,編譯成最終的apk。這其實預示着,如果接觸了aapt2的工程,對于有上百個module的工程,如果隻有一個module變更,隻需要使用aapt2編譯單一的module,然後對這100個module使用link指令,就可以實作幾乎是一個增量的效果。因為編譯的耗時過程隻進行一次就可以達到新的資源包,這是目前aapt2整個技術的實作。aapt2在整個6.0或7.0的源碼裡面,做了一個很大的變化。我們目前已經實作了使用aapt2打包資源包的功能,之後也會看整個aapt2是否值得我們去接入實作。
freeline後期也會對最近比較流行的kotlin、jack等做跟進,希望未來可以對這兩個工具做更好的支援,社群開發等很多新的項目都在使用kotlin,包括阿裡内網。jack也是google前幾年推出的。希望freeline在未來的疊代中能夠支援這兩個編譯工具。
如何內建?
現在freeline的內建非常簡單,直接搜尋freeline,就會有一個插件,一鍵安裝後就會自動配置內建整個環境。如果工程比較複雜,有幾十個module,需要特殊配置,也可以到官網上參考文檔。freeline以後也會繼續向一鍵內建與無縫內建發展。
freeline的開源曆程
最後介紹一下freeline的整個開源曆程:
(1)freeline始于2015年10月,最初是一個3000多行的python檔案,僅支援基于maven建構的mpsss架構工程,最長可達30分鐘的編譯時間,後來縮短為10s增量編譯,工程效能達到質的飛躍。
(2)後來在2016年5月進行重構,能支gradle建構的android工程,并在阿裡集團内部開源。緊接着在2016年8月實作開源,當天登上了github trending總榜。
(3)在2016年12月,freeline又實作了發展,獲得多個技術公衆号的推薦與轉載,連續一個月出現在github java trending榜單上,社群内開始有多篇在讨論freeline的技術文章。我們得到了apt、retrolambda與databinding的支援,并進行持續的相容性提升,推出了ide插件,不斷滿足社群的需求。