Gradle Builds Everything —— 基礎概念

提到 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 的構建任務,其實網上有不少文章介紹了,無非是介紹任務的定義方式,任務的doFirstdoLast,可是不多介紹其餘的元素,咱們從 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」
公衆號

相關文章
相關標籤/搜索