增量編譯是相對全量編譯而言的。所謂增量編譯,是指當源程序的局部發生變動後進從新編譯的工做只限於修改的部分及與之相關部分的內容,而不須要對所有代碼進行編譯。增量編譯對軟件開發,尤爲是在調試期,能夠大大縮短編譯時間, 提升編譯效率。html
而全量編譯指的是,當用戶源程序被局部修改後從新編譯代碼會涉及所有源代碼,並不僅限於局部修改及其相關部分。換句話說,不管修改了什麼,全量編譯都將進行一次全新的完整的編譯,並不基於上一次的編譯基礎。java
通常來講,在軟件開發中,全量編譯用於版本的構建與發佈,比較耗費時間和資源。而處於調試階段的程序,通常都採用增量編譯,這樣對於問題的定位和解決都比省時省力。在Android開發中,隨着工程代碼量膨脹,編譯耗時也愈來愈長,拖慢了開發效率,所以Android官方推出了Instant Run和Apply Changes等增量更新的方案。android
Instant Run是Android Studio 2.0版本推出的一個增量編譯功能,使用Instant Run功能時,須要在build.gradle 文件中將 minSdkVersion 設置爲 15 或以上時,而且爲另外得到最佳性能,能夠將 minSdkVersion 設置爲 21 或更高。緩存
以前在Android Studio 3.0版本,gradle爲2.14.1的版本中作過一個測試,編譯一個簡單的Demo項目從以前的10秒下降到大概二、3秒。默認狀況下,Instant Run是關閉的,若是要開啓Instant Run,能夠在Settings中打開Instant Run,如需所示。
不過,Android Studio在3.5版本廢棄了Instant Run,並使用HotSwap替代了Instant Run,以下圖所示。
關於Instant Run的一些原理方面的內容,能夠參考我以前的文章介紹:深刻理解Android Instant Run運行機制。服務器
在Android Studio 3.5及其以上版本,官方提供了Apply Changes,使用Apply Changes時,須要知足如下兩個條件:併發
當咱們使用Android Studio運行項目後,會在菜單欄看見3個按鈕,分別用來控制應用重啓,以下圖所示。app
如上圖所示,從左到右的按鈕分別表示【Run】、【Apply Changes 】和【Apply Code Changes】。框架
不過,因爲Apply Changes僅支持在Android 8.0 或者更高版本的手機上運行,而且實際操做時在工程中帶來的提速效果也不明顯。ide
除了官方的方案外,阿里巴巴客戶端團隊還基於動態替換研發了一款針對Android平臺的增量編譯工具,它能夠充分利用緩存文件,在幾秒鐘內迅速地對代碼的改動進行編譯並部署到設備上,有效地減小了平常開發中的大量從新編譯與安裝的耗時。工具
性能方面:內部採用了相似Facebook的開源工具buck的多工程多任務併發思想:端口掃描,代碼掃描,併發編譯, 併發dx,併發merge dex等策略,在多核機器上有明顯加速效果,另外在class及dex,resources層面做了相應緩存策略,作到真正增量開發,另外引入並優化 buck的部分加速組件dx,DexMerger,資源編譯方面,深刻改造了Aapt資源編譯流程,當資源發生改變時候,秒級完成增量包編譯,其中增量包 僅含最小的變動集合(10Kb~數百Kb內),後期也被運用到線上進行資源/代碼動態替換。相比目前instant- run,buck,layoutcast等方案快數倍速度。
不過,Freeline一樣存在着一些不可忽視的問題。首先是不支持Kotlin,這在Kotlin已經被谷歌官宣爲Android開發首選語言的今天,是比較致命的。另外,不支持刪除帶id的資源,不然可能致使資源編譯流程出錯。
另一個潛在的問題是,爲了確保編譯速度,Freeline是犧牲了一部分正確性的。例如,在改動公有靜態常量的時候,只會編譯對應的類文件,而引用到該常量的其餘類,並不會參與編譯的。因爲常量內聯優化的存在,就可能致使這些類在運行時,使用的仍然是舊的值,進而出現改動不生效的問題。
對於Android是如何從源碼到安裝包的過程,能夠參考Android官方給的一幅圖,主要會經歷編譯、連接和簽名等操做。
上面展現的是Android源碼編譯成安裝包的過程,而增量更新的完整的流程是: 【修改代碼】->【編譯工程】->【安裝APK】->【運行驗證】。
對於編譯階段,首先是收集工程中的全部資源文件進行編譯,獲得資源包以及資源索引類。隨後資源索引類會跟隨工程的全部源代碼文件一塊兒被編譯爲字節碼文件,而且字節碼文件還須要被進一步編譯爲Dex文件,這樣才能被Android虛擬機所識別。
Android的編譯打包會分爲如下幾個階段:
資源文件編譯
下圖完整的演示了Android編譯期和運行期的整個步驟。
Android增量編譯分爲代碼增量和資源增量,Android早期的Instant Run方案在資源上並非增量的,而是把整個應用的資源打成資源包,推送至手機的,所以效率極低。
谷歌在支持multidex以後(即典型的65535問題),Android打包後會存在多個dex文件,運行時加載類時,會從一個dexList依次查找,找到則返回,利用這個原理能夠把增量的代碼打包成dex文件,插入到dexList的前邊,這樣就能夠完成類的替換。
關於代碼的增量編譯須要考慮兩個主要的問題,即獲取改動文件並進行編譯、對依賴的代碼進行編譯。關於代碼的增量編譯,能夠參考QQ音樂的增量編譯方案:QQ音樂Android編譯提速之路
資源編譯與代碼增量是相似的,即先收集被改動的資源,而後進行編譯。Android的資源編譯主要使用的是aapt或者aapt2。通常來講如今都是使用aapt2來進行資源的打包編譯,由於aapt工具是不支持單個資源編譯的。
aapt2(Android 資源打包工具)是Android Studio 和 Android Gradle 插件使用它來編譯和打包應用的資源構建工具。aapt2 會解析資源、爲資源編制索引,並將資源編譯爲針對 Android 平臺進行過優化的二進制格式。
使用aapt2進行資源打包編譯時,分爲編譯(compile)與連接(link) 兩步,在編譯階段,負責將單個或者多個資源編譯爲二進制文件;連接階段,則負責合併全部二進制文件再打包。
關於資源的增量編譯,能夠參考QQ音樂的增量編譯方案:QQ音樂Android編譯提速之路
當項目通過屢次迭代以後,就會遇到各類各樣的問題,而編譯慢是每一個成熟 Android 團隊都沒法迴避的問題。在以前有贊零售 Android 團隊的技術分享中,整個Android項目有 25 個業務模塊,擁有 45W+ 行源代碼(Java + Kotlin)以及多個構建 Flavor。小夥伴在進行需求開發時,平均的增量編譯構建時間達到了兩分鐘,再加上一些 Gradle 配置與APK安裝過程,基本上驗證一行代碼的修改須要近三分鐘(MacBook Pro 13-inch, 2016, i5-8G),這樣的狀況大大下降了團隊的開發效率。
在 Savitar 誕生以前,咱們嘗試了社區中一些成熟的解決方案,如 BUCK、Freeline、InstantRun 等知名框架。不過調研下來,都或多或少的存在一些問題。
好比FaceBook的BUCK框架,自身有強大的構建系統,經過增量構建緩存機制,能夠有效提高編譯的速度,可是其使用和配置過於複雜,對於工程的入侵比較大,且對於一些 Databinding、 Kotlin 等 Android 的特性支持還有欠缺。
其次是阿里巴巴開源的Freeline ,Freeline以其極快的部署速度出名,但對咱們來講致命缺點是不支持 Kotlin。
InstantRun 是 Google 推薦的加速方式,擁有最全面的支持性,但因爲咱們是多進程的工程,而且 InstantRun 在編譯時的一些準備 Task 也會消耗一些時間,在實踐過程當中發現加速並不明顯。
Savitar 是有贊 Android 團隊增量編譯提效方案,它可以有效減小模塊修改編譯時間,包含配套 IDE 插件,使用方便,具備以下一些顯著的特色。
如圖所示,Savitar 總體分紅四個部分:
下圖演示了Savitar從代碼修改到完成修改產物加載運行的完整過程。
能夠發現,從代碼修改到完成修改產物主要經歷瞭如下幾個步驟:
關於Savitar是如何從獲取改動信息到完成加載,能夠參考Android 增量編譯提效方案Savitar的詳細介紹。
爲了方便開發者使用Savitar實現增量更新,Savitar 開發了一款 IDE 插件,只須要一鍵觸發就能夠完成整個編譯打包流程。首先,打開Android Studio ,而後依次選擇 【Preference】 -> 【Plugin】 搜索Savitar並安裝,以下圖所示。
安裝完成後重啓 IDE,而後在 Android Studio 中工具欄就會出現 Savitar 的圖標,以下圖所示。
點擊圖標後,能夠在 Savitar Window 看到工具編譯、打包、推送整個運行過程,包含錯誤信息,以下圖所示。
以下所示,有下面一段代碼:
import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) buttonCpu.setOnClickListener { ... //點擊事件 } } }
對於上面的代碼,想必使用過 Kotlin 的 Android 同窗並不會陌生,利用 Kotlinx 特性,能夠在 .kt 代碼中使用 Xml 中定義過組件Id直接獲取 View 實例進行操做,極大減小 UI 開發成本。
可是上面代碼中的 import 並非一個普通的形式,這樣的語法若是直接使用標準 kotlinc 進行編譯,會出現找不到 import 錯誤。此時須要藉助到 Kotlin 編譯器插件,在 Kotlin 編譯時傳入 Kotlinx 對應插件的 Jar 地址和參數,就能夠完成包含 Kotlinx 語法的文件編譯。
sh kotlinc -Xplugin=lib/android-extensions-compiler.jar -P plugin:org.jetbrains.kotlin.android:package=${package_name} -P plugin:org.jetbrains.kotlin.android:variant='${flovar};${resource_package}'
文檔參考 Kotlin 編譯器插件
在使用 Android Studio 開發過程當中,Kotlin 編譯所需的依賴包都是由 IDE 自動管理,可是 Savitar 是使用 Shell 實現,這樣的狀況下面就須要關心這個編譯工具的問題了。咱們將獲取 Kotlin 編譯依賴的邏輯放在 Savitar 運行環境檢測邏輯中,在檢測到沒有依賴包的狀況下會自動從內網服務器下載對應版本的庫,完成 Kotlin 代碼編譯。