提到 Gradle,熟悉 Android 的人都不會陌生,在咱們開始把 Android Studio 這個 IDE 扶正的時候,gradle 就完全進入了咱們的視野。可是大多數人對於 gradle 執行構建和構建流程都比較陌生,本文從編寫 Gradle Plugin 的角度,但願把 Gradle 體系的一些基礎結構能講明白。html
首先咱們明白,gradle 的工做是把全部的構建動做管理起來 —— 任務是否應該執行,何時執行,執行某個任務前先作一些什麼事情,某幾個動做是否能夠並行執行。
對於 gradle plugin 的編寫就是爲了幫咱們完成這些事情。若是你單單從任務的緯度去看這個問題的話,又會想到若是 B 須要 A 的產物的話,是否須要把 A 和 B 進行一些耦合。顯然,對於任務間的解耦,Gradle 也作了。java
上面咱們提到了「任務」這個詞,任務是什麼呢?一個任務咱們能夠理解爲把一次指定的輸入,轉換成想要的輸出。好比「編譯」這個動做,就是把 .java 文件編譯成 .class,或者執行 aapt,把資源文件編譯成一個resource.ap_
文件等。任務的基礎就是這麼簡單,而後爲了加快執行速度,gradle 增長了 UP-TO-DATE 檢查(只要輸入和輸出的文件不發生變化,那麼這個任務就再也不執行),也增長了 incremental build 的特性(下一次的編譯,並不僅是把.class
所有刪除,從新編譯一次這樣粗暴,而是隻編譯變化了幾個文件)併發
在這種細顆粒度的狀況下,咱們對於任務執行的正確性和效率都有了保障。ide
關於 Gradle 的構建任務,其實網上有不少文章介紹了,無非是介紹任務的定義方式,任務的doFirst
和doLast
,可是不多介紹其餘的元素,咱們從 gradle plugin 的視角介紹一下這些概念。在一切開始以前,咱們要了解下 gradle 這個容器的一些最最基礎的流程 —— gradle 構建生命週期。函數
官方文檔: https://docs.gradle.org/curre...
如文檔所示,gradle 在執行的時候,會經歷三個過程 —— 初始化,配置,執行。初始化過程對於咱們來講,體感比較弱;配置階段是一個重要階段,咱們須要告訴每個 Task,它的輸入文件是什麼(好比源碼文件,資源文件),輸出文件或者文件夾是什麼(好比編譯後的 .class 文件,ap_ 等資源包放在哪一個文件夾下)等等。那麼執行階段,就是真正執行任務的時候了,咱們這時候須要在執行的函數中,拿到在配置階段定義的 Input,而後生產出 Output,放到規定的目錄下,或者寫入指定的文件便可。gradle
對於咱們來講,理解生命週期尤其重要,若是你在configuration
階段去獲取一個 task 的結果,從邏輯上來講是很愚蠢的。因此你很須要知道你的代碼是在「什麼狀態下」執行這一步操做。ui
咱們知道了生命週期之後,就要開始思考一個問題,好比 B 任務的一些輸入依賴於 A 任務的一些輸出,這時候就須要配置 B 任務依賴 A 任務,那麼我如何保證這一點呢?this
有一個辦法,那就是對 B 任務調用顯式依賴B.dependsOn(A)
這樣 B 必定在 A 以後執行的,B 任務中對於某個由 A 產生的文件的讀取是必定能讀到的。不錯,它是個好辦法,但問題就在於,這樣的指定方式耦合度很是高,若是你須要加入一些對A
產物的一些修改,而後再傳給B
的時候,就沒有任何辦法了。B
同時知道了A
的存在,若是咱們這時候不但願由A
任務提供這個文件,而是由A'
來提供這個輸出,在這裏也作不到,因此須要換一個思路。spa
Gradle 提供了並使用了很是多像 Provider,Property,FileCollection 之類這樣的類。看名字咱們大概能知道,這些方法都提供了一個 get() 方法,獲取到裏面保存的實例。可是 Gradle 對於這個 get() 方法賦予了更多的意義,它能夠把依賴關係放進去,當你調用get()
的時候,能夠檢查它的依賴的任務是否已經執行完成,若是已經完成,那麼再返回這個值。code
@NonExtensible public interface Provider<T> { /** * Returns the value of this provider if it has a value present, otherwise throws {@code java.lang.IllegalStateException}. * * @return the current value of this provider. * @throws IllegalStateException if there is no value present */ T get(); //..... }
有了上面這個特性,咱們定義起依賴關係就簡單多了,咱們把一個任務的輸出文件用 Provider 包裹起來,也就是Provider<File>
這樣的類型提供,由 Gradle 或者自行爲這些 Provider 設置dependsOn
,而後再把這些 Provider 分發給其餘 Task。
另外的 Task 只要保證它只在執行階段去調用這些 Provider 的 get 方法便可。Provider 只是一種意圖,所以他們能夠先把 Provider 存到 Task 實例的成員變量裏,同時使用 Gradle 提供的@Input/@InputFile/@OutputFile
等註解爲這些 Provider 的 getter 進行標註,這樣能讓 Gradle 把這些值管理起來。
這樣咱們解決了第一個問題 —— Task 之間不在顯式依賴。若是咱們想實如今 Task A 和 Task B 之間作一些 Hook 的話,咱們這時候要對 Provider 作一個管理,咱們能夠作一個全局管理器,爲每個產物集合作一個名字或者枚舉的標記,而後對對應的標記定義一系列的動做,好比替換這個標記的產物,或者追加產物等,以便於後續的任務能更好的處理這裏產生的產物。
這張圖是原來的顯式依賴方式
解耦後的方式是
這樣任務和任務之間就這麼聯繫在了一塊兒,當咱們執行一條熟悉的命令:
./gradlew assembleDebug
它會把依賴產物的全部 task 所有執行一遍,事實上,assembleDebug
這個任務根本不知道本身依賴了哪些具體的任務,它只知道本身「須要」什麼,產出什麼(apk)。
上面講了任務依賴相關的理論知識,咱們來舉一個具體的例子,就以assembleDebug
爲例。
咱們把事情說的簡單點,好比assembleDebug
的任務是把全部已經處理好的 dex,resources,assets 打包成一個 apk,那麼這個 input 就是前面提到的三個,output 是 apk。咱們在assembleDebug
這個 Task 裏面會看到以下的東西(僞代碼):
class AssembleDebugTask { private Provider<File> dexInput; private Provider<File> resourcesInput; private Provider<File> assetsInput; private Provider<File> outputAPK; @InputFile public Provider<File> getDexInput() { return dexInput; } @InputFile public Provider<File> getResourcesInput() { return resourcesInput; } @InputFile public Provider<File> getAssetsInput() { return assetsInput; } @OutputFile public Provider<File> getOutputAPK() { return outputAPK; } }
以上是對產物的定義,那麼在執行任務的過程當中,會有這樣的邏輯:
public void doTaskAction() { File dexInput = this.dexInput.get(); }
在這一步的過程當中,Gradle 會去檢查這個 Provider 的來源,有沒有builtBy
屬性,若是有的話,會先執行buildBy
的 Task,好比咱們知道Dex
的文件必定來源於產生 Dex 的任務,那麼若是咱們定義這個任務叫DexTask
的話,就會先執行DexTask
這個任務,纔會繼續執行assembleDebug
了。
事實上爲了加快效率,標記了@Input
之類的註解的屬性,gradle 在檢查任務的時候,會提早去執行相關的依賴,由於在這個過程當中,它能夠動用併發的方式,並行執行幾個任務,好比咱們這依賴了三個輸入,那麼能夠並行執行這三個任務,等到都執行完了,再去執行assemble
的任務,這時候調用get
就能直接返回值了。
歡迎關注個人公衆號「TalkWithMobile」