深度學(xué)習(xí) Gradle 自動(dòng)化構(gòu)建技術(shù)(三、Gradle 核心解密)

成為一名優(yōu)秀的Android開發(fā),需要一份完備的知識(shí)體系,在這里,讓我們一起成長(zhǎng)為自己所想的那樣~。

前言

從明面上看,Gradle 是一款強(qiáng)大的構(gòu)建工具,而且許多文章也僅僅都把 Gradle 當(dāng)做一款工具對(duì)待。但是,Gradle 不僅僅是一款強(qiáng)大的構(gòu)建工具,它看起來(lái)更像是一個(gè)編程框架。Gradle 的組成可以細(xì)分為如下三個(gè)方面

  • 1)、groovy 核心語(yǔ)法包括 groovy 基本語(yǔ)法、閉包、數(shù)據(jù)結(jié)構(gòu)、面向?qū)ο蟮鹊?/strong>。
  • 2)、Android DSL(build scrpit block)Android 插件在 Gradle 所特有的東西,我們可以在不同的 build scrpit block 中去做不同的事情。
  • 3)、Gradle API包含 Project、Task、Setting 等等(本文重點(diǎn))

可以看到,Gradle 的語(yǔ)法是以 groovy 為基礎(chǔ)的,而且,它還有自己獨(dú)有的 API,所以我們可以把 Gradle 認(rèn)作是一款編程框架,利用 Gradle 我們可以在編程中去實(shí)現(xiàn)項(xiàng)目構(gòu)建過(guò)程中的所有需求。需要注意的是,想要隨心所欲地使用 Gradle,我們必須提前掌握好 groovy,如果對(duì) groovy 還不是很熟悉的建議看看 《深入探索Gradle自動(dòng)化構(gòu)建技術(shù)(二、Groovy 筑基篇)》 一文。

需要注意的是,Groovy 是一門語(yǔ)言,而 DSL 一種特定領(lǐng)域的配置文件,Gradle 是基于 Groovy 的一種框架工具,而 gradlew 則是 gradle 的一個(gè)兼容包裝工具。

一、Gradle 優(yōu)勢(shì)

1、更好的靈活性

在靈活性上,Gradle 相對(duì)于 Maven、Ant 等構(gòu)建工具, 其 提供了一系列的 API 讓我們有能力去修改或定制項(xiàng)目的構(gòu)建過(guò)程。例如我們可以 利用 Gradle 去動(dòng)態(tài)修改生成的 APK 包名,但是如果是使用的 Maven、Ant 等工具,我們就必須等生成 APK 后,再手動(dòng)去修改 APK 的名稱。

2、更細(xì)的粒度

在粒度性上,使用 Maven、Ant 等構(gòu)建工具時(shí),我們的源代碼和構(gòu)建腳本是獨(dú)立的,而且我們也不知道其內(nèi)部的處理是怎樣的。但是,我們的 Gradle 則不同,它 從源代碼的編譯、資源的編譯、再到生成 APK 的過(guò)程中都是一個(gè)接一個(gè)來(lái)執(zhí)行的。

此外,Gradle 構(gòu)建的粒度細(xì)化到了每一個(gè) task 之中。并且它所有的 Task 源碼都是開源的,在我們掌握了這一整套打包流程后,我們就可以通過(guò)去修改它的 Task 去動(dòng)態(tài)改變其執(zhí)行流程。例如 Tinker 框架的實(shí)現(xiàn)過(guò)程中,它通過(guò)動(dòng)態(tài)地修改 Gradle 的打包過(guò)程生成 APK 的同時(shí),也生成了各種補(bǔ)丁文件。

3、更好的擴(kuò)展性

在擴(kuò)展性上,Gradle 支持插件機(jī)制,所以我們可以復(fù)用這些插件,就如同復(fù)用庫(kù)一樣簡(jiǎn)單方便。

4、更強(qiáng)的兼容性

Gradle 不僅自身功能強(qiáng)大,而且它還能 兼容所有的 Maven、Ant 功能,也就是說(shuō),Gradle 吸取了所有構(gòu)建工具的長(zhǎng)處

可以看到,Gradle 相比于其它構(gòu)建工具,其好處不言而喻,而其 最核心的原因就是因?yàn)?Gradle 是一套編程框架

二、Gradle 構(gòu)建生命周期

Gradle 的構(gòu)建過(guò)程分為 三部分:初始化階段、配置階段和執(zhí)行階段。下面分別來(lái)詳細(xì)了解下它們。

1、初始化階段

首先,在這個(gè)階段中,會(huì)讀取根工程中的 setting.gradle 中的 include 信息,確定有多少工程加入構(gòu)建,然后,會(huì)為每一個(gè)項(xiàng)目(build.gradle 腳本文件)創(chuàng)建一個(gè)個(gè)與之對(duì)應(yīng)的 Project 實(shí)例,最終形成一個(gè)項(xiàng)目的層次結(jié)構(gòu)。 與初始化階段相關(guān)的腳本文件是 settings.gradle,而一個(gè) settings.gradle 腳本對(duì)應(yīng)一個(gè) Settings 對(duì)象,我們最常用來(lái)聲明項(xiàng)目的層次結(jié)構(gòu)的 include 就是 Settings 對(duì)象下的一個(gè)方法,在 Gradle 初始化的時(shí)候會(huì)構(gòu)造一個(gè) Settings 實(shí)例對(duì)象,以執(zhí)行各個(gè) Project 的初始化配置。

settings.gradle

settings.gradle 文件中,我們可以 在 Gradle 的構(gòu)建過(guò)程中添加各個(gè)生命周期節(jié)點(diǎn)監(jiān)聽,其代碼如下所示:

include ':app'
gradle.addBuildListener(new BuildListener() {
    void buildStarted(Gradle var1) {
        println '開始構(gòu)建'
    }
    void settingsEvaluated(Settings var1) {
        // var1.gradle.rootProject 這里訪問(wèn) Project 對(duì)象時(shí)會(huì)報(bào)錯(cuò),
        // 因?yàn)檫€未完成 Project 的初始化。
        println 'settings 評(píng)估完成(settings.gradle 中代碼執(zhí)行完畢)'
    }
    void projectsLoaded(Gradle var1) {
        println '項(xiàng)目結(jié)構(gòu)加載完成(初始化階段結(jié)束)'
        println '初始化結(jié)束,可訪問(wèn)根項(xiàng)目:' + var1.gradle.rootProject
    }
    void projectsEvaluated(Gradle var1) {
        println '所有項(xiàng)目評(píng)估完成(配置階段結(jié)束)'
    }
    void buildFinished(BuildResult var1) {
        println '構(gòu)建結(jié)束 '
    }
})
復(fù)制代碼

編寫完相應(yīng)的 Gradle 生命周期監(jiān)聽代碼之后,我們就可以在 Build 輸出界面看到如下信息:

Executing tasks: [clean, :app:assembleSpeedDebug] in project
/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
settings評(píng)估完成(settins.gradle中代碼執(zhí)行完畢)
項(xiàng)目結(jié)構(gòu)加載完成(初始化階段結(jié)束)
初始化結(jié)束,可訪問(wèn)根項(xiàng)目:root project 'Awesome-WanAndroid'
Configuration on demand is an incubating feature.
> Configure project :app
gradlew version > 4.0
WARNING: API 'variant.getJavaCompiler()' is obsolete and has been
replaced with 'variant.getJavaCompileProvider()'.
It will be removed at the end of 2019.
For more information, see
https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variant.getJavaCompiler(), use
-Pandroid.debug.obsoleteApi=true on the command line to display more
information.
skip tinyPicPlugin Task!!!!!!
skip tinyPicPlugin Task!!!!!!
所有項(xiàng)目評(píng)估完成(配置階段結(jié)束)
> Task :clean UP-TO-DATE
:clean spend 1ms
...
> Task :app:clean
:app:clean spend 2ms
> Task :app:packageSpeedDebug
:app:packageSpeedDebug spend 825ms
> Task :app:assembleSpeedDebug
:app:assembleSpeedDebug spend 1ms
構(gòu)建結(jié)束 
Tasks spend time > 50ms:
    ...
復(fù)制代碼

此外,在 settings.gradle 文件中,我們可以指定其它 project 的位置,這樣就可以將其它外部工程中的 moudle 導(dǎo)入到當(dāng)前的工程之中了。示例代碼如下所示:

if (useSpeechMoudle) {
    // 導(dǎo)入其它 App 的 speech 語(yǔ)音模塊
    include "speech"
    project(":speech").projectDir = new File("../OtherApp/speech")
}
復(fù)制代碼

2、配置階段

配置階段的任務(wù)是 執(zhí)行各項(xiàng)目下的 build.gradle 腳本,完成 Project 的配置,與此同時(shí),會(huì)構(gòu)造 Task 任務(wù)依賴關(guān)系圖以便在執(zhí)行階段按照依賴關(guān)系執(zhí)行 Task。而在配置階段執(zhí)行的代碼通常來(lái)說(shuō)都會(huì)包括以下三個(gè)部分的內(nèi)容,如下所示:

  • 1)、build.gralde 中的各種語(yǔ)句
  • 2)、閉包
  • 3)、Task 中的配置段語(yǔ)句。

需要注意的是,執(zhí)行任何 Gradle 命令,在初始化階段和配置階段的代碼都會(huì)被執(zhí)行

3、執(zhí)行階段

在配置階段結(jié)束后,Gradle 會(huì)根據(jù)各個(gè)任務(wù) Task 的依賴關(guān)系來(lái)創(chuàng)建一個(gè)有向無(wú)環(huán)圖,我們可以通過(guò) Gradle 對(duì)象的 getTaskGraph 方法來(lái)得到該有向無(wú)環(huán)圖 => TaskExecutionGraph,并且,當(dāng)有向無(wú)環(huán)圖構(gòu)建完成之后,所有 Task 執(zhí)行之前,我們可以通過(guò) whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 來(lái)接收相應(yīng)的通知,其代碼如下所示:

gradle.getTaskGraph().addTaskExecutionGraphListener(new
TaskExecutionGraphListener() {
    @Override
    void graphPopulated(TaskExecutionGraph graph) {
    }
})
復(fù)制代碼

然后,Gradle 構(gòu)建系統(tǒng)會(huì)通過(guò)調(diào)用 gradle <任務(wù)名> 來(lái)執(zhí)行相應(yīng)的各個(gè)任務(wù)。

4、Hook Gradle 各個(gè)生命周期節(jié)點(diǎn)

整個(gè) Gradle 生命周期的流程包含如下 四個(gè)部分

  • 1)、首先,解析 settings.gradle 來(lái)獲取模塊信息,這是初始化階段。
  • 2)、然后,配置每個(gè)模塊,配置的時(shí)候并不會(huì)執(zhí)行 task。
  • 3)、接著,配置完了以后,有一個(gè)重要的回調(diào) project.afterEvaluate,它表示所有的模塊都已經(jīng)配置完了,可以準(zhǔn)備執(zhí)行 task 了。
  • 4)、最后,執(zhí)行指定的 task 及其依賴的 task。

在 Gradle 構(gòu)建命令中,最為復(fù)雜的命令可以說(shuō)是 gradle build 這個(gè)命令了,因?yàn)轫?xiàng)目的構(gòu)建過(guò)程中需要依賴很多其它的 task。

注意事項(xiàng)

  • 1)、每一個(gè) Hook 點(diǎn)對(duì)應(yīng)的監(jiān)聽器一定要在回調(diào)的生命周期之前添加。
  • 2)、如果注冊(cè)了多個(gè) project.afterEvaluate 回調(diào),那么執(zhí)行順序?qū)⑴c注冊(cè)順序保持一致。

5、獲取構(gòu)建各個(gè)階段、任務(wù)的耗時(shí)情況

了解了 Gradle 生命周期中的各個(gè) Hook 方法之后,我們就可以 利用它們來(lái)獲取項(xiàng)目構(gòu)建各個(gè)階段、任務(wù)的耗時(shí)情況,在 settings.gradle 中加入如下代碼即可:

long beginOfSetting = System.currentTimeMillis()
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
def beginOfProjectExcute
gradle.projectsLoaded {
    println '初始化階段,耗時(shí):' + (System.currentTimeMillis() -
beginOfSetting) + 'ms'
}
gradle.beforeProject { project ->
    if (!configHasBegin) {
        configHasBegin = true
        beginOfConfig = System.currentTimeMillis()
    }
    beginOfProjectConfig.put(project, System.currentTimeMillis())
}
gradle.afterProject { project ->
    def begin = beginOfProjectConfig.get(project)
    println '配置階段,' + project + '耗時(shí):' +
(System.currentTimeMillis() - begin) + 'ms'
}
gradle.taskGraph.whenReady {
    println '配置階段,總共耗時(shí):' + (System.currentTimeMillis() -
beginOfConfig) + 'ms'
    beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
    task.doFirst {
        task.ext.beginOfTask = System.currentTimeMillis()
    }
    task.doLast {
        println '執(zhí)行階段,' + task + '耗時(shí):' +
(System.currentTimeMillis() - task.beginOfTask) + 'ms'
    }
}
gradle.buildFinished {
    println '執(zhí)行階段,耗時(shí):' + (System.currentTimeMillis() -
beginOfProjectExcute) + 'ms'
}
復(fù)制代碼

在 Gradle 中,執(zhí)行每一種類型的配置腳本就會(huì)創(chuàng)建與之對(duì)應(yīng)的實(shí)例,而在 Gradle 中如 三種類型的配置腳本,如下所示:

  • 1)、Build Scrpit對(duì)應(yīng)一個(gè) Project 實(shí)例,即每個(gè) build.gradle 都會(huì)轉(zhuǎn)換成一個(gè) Project 實(shí)例
  • 2)、Init Scrpit對(duì)應(yīng)一個(gè) Gradle 實(shí)例,它在構(gòu)建初始化時(shí)創(chuàng)建,整個(gè)構(gòu)建執(zhí)行過(guò)程中以單例形式存在。
  • 3)、Settings Scrpit對(duì)應(yīng)一個(gè) Settings 實(shí)例,即每個(gè) settings.gradle 都會(huì)轉(zhuǎn)換成一個(gè) Settings 實(shí)例。

可以看到,一個(gè) Gradle 構(gòu)建流程中會(huì)由一至多個(gè) project 實(shí)例構(gòu)成,而每一個(gè) project 實(shí)例又是由一至多個(gè) task 構(gòu)成。下面,我們就來(lái)認(rèn)識(shí)下 Project。

三、Project

Project 是 Gradle 構(gòu)建整個(gè)應(yīng)用程序的入口,所以它非常重要,我們必須對(duì)其有深刻地了解。不幸的是,網(wǎng)上幾乎沒(méi)有關(guān)于 project 講解的比較好的文章,不過(guò)沒(méi)關(guān)系,下面,我們將會(huì)一起來(lái)深入學(xué)習(xí) project api 這部分。

由前可知,每一個(gè) build.gradle 都有一個(gè)與之對(duì)應(yīng)的 Project 實(shí)例,而在 build.gradle 中,我們通常都會(huì)配置一系列的項(xiàng)目依賴,如下面這個(gè)依賴:

implementation 'com.github.bumptech.glide:glide:4.8.0'
復(fù)制代碼

類似于 implementation、api 這種依賴關(guān)鍵字,在本質(zhì)上它就是一個(gè)方法調(diào)用,在上面,我們使用 implementation() 方法傳入了一個(gè) map 參數(shù),參數(shù)里面有三對(duì) key-value,完整寫法如下所示:

implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0'
復(fù)制代碼

當(dāng)我們使用 implementation、api 依賴對(duì)應(yīng)的 aar 文件時(shí),Gradle 會(huì)在 repository 倉(cāng)庫(kù) 里面找到與之對(duì)應(yīng)的依賴文件,你的倉(cāng)庫(kù)中可能包含 jcenter、maven 等一系列倉(cāng)庫(kù),而每一個(gè)倉(cāng)庫(kù)其實(shí)就是很多依賴文件的集合服務(wù)器, 而他們就是通過(guò)上述的 group、name、version 來(lái)進(jìn)行歸類存儲(chǔ)的

1、Project 核心 API 分解

在 Project 中有很多的 API,但是根據(jù)它們的 屬性和用途 我們可以將其分解為 六大部分,如下圖所示:

對(duì)于 Project 中各個(gè)部分的作用,我們可以先來(lái)大致了解下,以便為 Project 的 API 體系建立一個(gè)整體的感知能力,如下所示:

  • 1)、Project API讓當(dāng)前的 Project 擁有了操作它的父 Project 以及管理它的子 Project 的能力
  • 2)、Task 相關(guān) API為當(dāng)前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常重要,我們將放到第四章來(lái)進(jìn)行講解。
  • 3)、Project 屬性相關(guān)的 ApiGradle 會(huì)預(yù)先為我們提供一些 Project 屬性,而屬性相關(guān)的 api 讓我們擁有了為 Project 添加額外屬性的能力
  • 4)、File 相關(guān) ApiProject File 相關(guān)的 API 主要用來(lái)操作我們當(dāng)前 Project 下的一些文件處理
  • 5)、Gradle 生命周期 API即我們?cè)诘诙轮v解過(guò)的生命周期 API。
  • 6)、其它 API添加依賴、添加配置、引入外部文件等等零散 API 的聚合

2、Project API

每一個(gè) Groovy 腳本都會(huì)被編譯器編譯成 Script 字節(jié)碼,而每一個(gè) build.gradle 腳本都會(huì)被編譯器編譯成 Project 字節(jié)碼,所以我們?cè)?build.gradle 中所寫的一切邏輯都是在 Project 類內(nèi)進(jìn)行書寫的。下面,我們將按照由易到難的套路來(lái)介紹 Project 的一系列重要的 API。

需要提前說(shuō)明的是,默認(rèn)情況下我們選定根工程的 build.gradle 這個(gè)腳本文件中來(lái)學(xué)習(xí) Project 的一系列用法,關(guān)于 getAllProject 的用法如下所示:

1、getAllprojects

getAllprojects 表示 獲取所有 project 的實(shí)例,示例代碼如下所示:

/**
 * getAllProjects 使用示例
 */
this.getProjects()

def getProjects() {
    println "<================>"
    println " Root Project Start "
    println "<================>"
    // 1、getAllprojects 方法返回一個(gè)包含根 project 與其子 project 的 Set 集合
    // eachWithIndex 方法用于遍歷集合、數(shù)組等可迭代的容器,
    // 并同時(shí)返回下標(biāo),不同于 each 方法僅返回 project
    this.getAllprojects().eachWithIndex { Project project, int index ->
        // 2、下標(biāo)為 0,表明當(dāng)前遍歷的是 rootProject
        if (index == 0) {
            println "Root Project is $project"
        } else {
            println "child Project is $project"
        }
    }
}
復(fù)制代碼

首先,我們使用了 def 關(guān)鍵字定義了一個(gè) getProjects 方法。然后,在注釋1處,我們調(diào)用了 getAllprojects 方法返回一個(gè)包含根 project 與其子 project 的 Set 集合,并鏈?zhǔn)秸{(diào)用了 eachWithIndex 遍歷 Set 集合。接著,在注釋2處,我們會(huì)判斷當(dāng)前的下標(biāo) index 是否是0,如果是,則表明當(dāng)前遍歷的是 rootProject,則輸出 rootProject 的名字,否則,輸出 child project 的名字。

下面,我們?cè)诿钚袌?zhí)行 ./gradlew clean,其運(yùn)行結(jié)果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評(píng)估完成(settings.gradle 中代碼執(zhí)行完畢)
項(xiàng)目結(jié)構(gòu)加載完成(初始化階段結(jié)束)
初始化結(jié)束,可訪問(wèn)根項(xiàng)目:root project 'Awesome-WanAndroid'
初始化階段,耗時(shí):5ms
Configuration on demand is an incubating feature.

> Configure project :
<================>
 Root Project Start 
<================>
Root Project is root project 'Awesome-WanAndroid'
child Project is project ':app'
配置階段,root project 'Awesome-WanAndroid'耗時(shí):284ms

> Configure project :app
...
配置階段,總共耗時(shí):428ms

> Task :app:clean
執(zhí)行階段,task ':app:clean'耗時(shí):1ms
:app:clean spend 2ms
構(gòu)建結(jié)束 
Tasks spend time > 50ms:
執(zhí)行階段,耗時(shí):9ms
復(fù)制代碼

可以看到,執(zhí)行了初始化之后,就會(huì)先配置我們的 rootProject,并輸出了對(duì)應(yīng)的工程信息。接著,便會(huì)執(zhí)行子工程 app 的配置。最后,執(zhí)行了 clean 這個(gè) task。

需要注意的是,rootProject 與其旗下的各個(gè)子工程組成了一個(gè)樹形結(jié)構(gòu),但是這顆樹的高度也僅僅被限定為了兩層。

2、getSubprojects

getSubprojects 表示獲取當(dāng)前工程下所有子工程的實(shí)例,示例代碼如下所示:

/**
 * getAllsubproject 使用示例
 */
this.getSubProjects()

def getSubProjects() {
    println "<================>"
    println " Sub Project Start "
    println "<================>"
    // getSubprojects 方法返回一個(gè)包含子 project 的 Set 集合
    this.getSubprojects().each { Project project ->
        println "child Project is $project"
    }
}
復(fù)制代碼

同 getAllprojects 的用法一樣,getSubprojects 方法返回了一個(gè)包含子 project 的 Set 集合,這里我們直接使用 each 方法將各個(gè)子 project 的名字打印出來(lái)。其運(yùn)行結(jié)果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評(píng)估完成(settings.gradle 中代碼執(zhí)行完畢)
...

> Configure project :
<================>
 Sub Project Start 
<================>
child Project is project ':app'
配置階段,root project 'Awesome-WanAndroid'耗時(shí):289ms

> Configure project :app
...
所有項(xiàng)目評(píng)估完成(配置階段結(jié)束)
配置階段,總共耗時(shí):425ms

> Task :app:clean
執(zhí)行階段,task ':app:clean'耗時(shí):1ms
:app:clean spend 2ms
構(gòu)建結(jié)束 
Tasks spend time > 50ms:
執(zhí)行階段,耗時(shí):9ms
復(fù)制代碼

可以看到,同樣在 Gradle 的配置階段輸出了子工程的名字。

3、getParent

getParent 表示 獲取當(dāng)前 project 的父類,需要注意的是,如果我們?cè)诟こ讨惺褂盟?,獲取的父類會(huì)為 null,因?yàn)楦こ虥](méi)有父類,所以這里我們直接在 app 的 build.gradle 下編寫下面的示例代碼:

...

> Configure project :
配置階段,root project 'Awesome-WanAndroid'耗時(shí):104ms

> Configure project :app
gradlew version > 4.0
my parent project is Awesome-WanAndroid
配置階段,project ':app'耗時(shí):282ms

...

所有項(xiàng)目評(píng)估完成(配置階段結(jié)束)
配置階段,總共耗時(shí):443ms

...
復(fù)制代碼

可以看到,這里輸出了 app project 當(dāng)前的父類,即 Awesome-WanAndroid project。

4、getRootProject

如果我們想在根工程僅僅獲取當(dāng)前的 project 實(shí)例該怎么辦呢?直接使用 getRootProject 即可在任意 build.gradle 文件獲取當(dāng)前根工程的 project 實(shí)例,示例代碼如下所示:

/**
 * 4、getRootProject 使用示例
 */
this.getRootPro()

def getRootPro() {
    def rootProjectName = this.getRootProject().name
    println "root project is $rootProjectName"
}
復(fù)制代碼

5、project

project 表示的是 指定工程的實(shí)例,然后在閉包中對(duì)其進(jìn)行操作。在使用之前,我們有必要看看 project 方法的源碼,如下所示:

    /**
     * <p>Locates a project by path and configures it using the given closure. If the path is relative, it is
     * interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p>
     *
     * @param path The path.
     * @param configureClosure The closure to use to configure the project.
     * @return The project with the given path. Never returns null.
     * @throws UnknownProjectException If no project with the given path exists.
     */
    Project project(String path, Closure configureClosure);
復(fù)制代碼

可以看到,在 project 方法中兩個(gè)參數(shù),一個(gè)是指定工程的路徑,另一個(gè)是用來(lái)配置該工程的閉包。下面我們看看如何靈活地使用 project,示例代碼如下所示:

/**
 * 5、project 使用示例
 */

// 1、閉包參數(shù)可以放在括號(hào)外面
project("app") { Project project ->
    apply plugin: 'com.android.application'
}

// 2、更簡(jiǎn)潔的寫法是這樣的:省略參數(shù)
project("app") {
    apply plugin: 'com.android.application'
}
復(fù)制代碼

使用熟練之后,我們通常會(huì)采用注釋2處的寫法。

6、allprojects

allprojects 表示 用于配置當(dāng)前 project 及其旗下的每一個(gè)子 project,如下所示:

/**
 * 6、allprojects 使用示例
 */

// 同 project 一樣的更簡(jiǎn)潔寫法
allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven {
            url "https://jitpack.io"
        }
        maven { url "https://plugins.gradle.org/m2/" }
    }
}
復(fù)制代碼

在 allprojects 中我們一般用來(lái)配置一些通用的配置,比如上面最常見的全局倉(cāng)庫(kù)配置。

7、subprojects

subprojects 可以 統(tǒng)一配置當(dāng)前 project 下的所有子 project,示例代碼如下所示:

/**
 * 7、subprojects 使用示例:
 *    給所有的子工程引入 將 aar 文件上傳置 Maven 服務(wù)器的配置腳本
 */
subprojects {
    if (project.plugins.hasPlugin("com.android.library")) {
        apply from: '../publishToMaven.gradle'
    }
}
復(fù)制代碼

在上述示例代碼中,我們會(huì)先判斷當(dāng)前 project 旗下的子 project 是不是庫(kù),如果是庫(kù)才有必要引入 publishToMaven 腳本。

3、project 屬性

目前,在 project 接口里,僅僅預(yù)先定義了 七個(gè) 屬性,其源碼如下所示:

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    /**
     * 默認(rèn)的工程構(gòu)建文件名稱
     */
    String DEFAULT_BUILD_FILE = "build.gradle";

    /**
     * 區(qū)分開 project 名字與 task 名字的符號(hào)
     */
    String PATH_SEPARATOR = ":";

    /**
     * 默認(rèn)的構(gòu)建目錄名稱
     */
    String DEFAULT_BUILD_DIR_NAME = "build";

    String GRADLE_PROPERTIES = "gradle.properties";

    String SYSTEM_PROP_PREFIX = "systemProp";

    String DEFAULT_VERSION = "unspecified";

    String DEFAULT_STATUS = "release";

    ...
}
復(fù)制代碼

幸運(yùn)的是,Gradle 提供了 ext 關(guān)鍵字讓我們有能力去定義自身所需要的擴(kuò)展屬性。有了它便可以對(duì)我們工程中的依賴進(jìn)行全局配置。下面,我們先從配置的遠(yuǎn)古時(shí)代講起,以便讓我們對(duì) gradle 的 全局依賴配置有更深入的理解。

ext 擴(kuò)展屬性

1、遠(yuǎn)古時(shí)代

在 AS 剛出現(xiàn)的時(shí)候,我們的依賴配置代碼是這樣的:

android {
    compileSdkVersion 27
    buildToolsVersion "28.0.3"
    ...
}
復(fù)制代碼

2、刀耕火種

但是這種直接寫值的方式顯示是不規(guī)范的,因此,后面我們使用了這種方式:

def mCompileSdkVersion = 27
def mBuildToolsVersion = "28.0.3"

android {
    compileSdkVersion mCompileSdkVersion
    buildToolsVersion mBuildToolsVersion
    ...
}
復(fù)制代碼

3、鐵犁牛耕

如果每一個(gè)子 project 都需要配置相同的 Version,我們就需要多寫很多的重復(fù)代碼,因此,我們可以利用上面我們學(xué)過(guò)的 subproject 和 ext 來(lái)進(jìn)行簡(jiǎn)化:

// 在根目錄下的 build.gradle 中
subprojects {
    ext {
        compileSdkVersion = 27
        buildToolsVersion = "28.0.3"
    }
}

// 在 app moudle 下的 build.gradle 中
android {
    compileSdkVersion this.compileSdkVersion
    buildToolsVersion this.buildToolsVersion
    ...
}
復(fù)制代碼

4、工業(yè)時(shí)代

使用 subprojects 方法來(lái)定義通用的擴(kuò)展屬性還是存在著很嚴(yán)重的問(wèn)題,它跟之前的方式一樣,還是會(huì)在每一個(gè)子 project 去定義這些被擴(kuò)展的屬性,此時(shí),我們可以將 subprojects 去除,直接使用 ext 進(jìn)行全局定義即可:

// 在根目錄下的 build.gradle 中
ext {
    compileSdkVersion = 27
    buildToolsVersion = "28.0.3"
}
復(fù)制代碼

5、電器時(shí)代

當(dāng)項(xiàng)目越來(lái)越大的時(shí)候,在根項(xiàng)目下定義的 ext 擴(kuò)展屬性越來(lái)越多,因此,我們可以將這一套全局屬性配置在另一個(gè) gradle 腳本中進(jìn)行定義,這里我們通常會(huì)將其命名為 config.gradle,通用的模板如下所示:

ext {

    android = [
            compileSdkVersion       : 27,
            buildToolsVersion       : "28.0.3",
            ...
            ]

    version = [
            supportLibraryVersion   : "28.0.0",
            ...
            ]

    dependencies = [
            // base
            "appcompat-v7"                      : "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}",
            ...
            ]

    annotationProcessor = [
            "glide_compiler"                    : "com.github.bumptech.glide:compiler:${version["glideVersion"]}",
            ...
            ]

    apiFileDependencies = [
            "launchstarter"                                   : "libs/launchstarter-release-1.0.0.aar",
            ...
            ]

    debugImplementationDependencies = [
            "MethodTraceMan"                                  : "com.github.zhengcx:MethodTraceMan:1.0.7"
    ]

    releaseImplementationDependencies = [
            "MethodTraceMan"                                  : "com.github.zhengcx:MethodTraceMan:1.0.5-noop"
    ]

    ...
}
復(fù)制代碼

6、更加智能化的現(xiàn)在

盡管有了很全面的全局依賴配置文件,但是,在我們的各個(gè)模塊之中,還是不得不寫一大長(zhǎng)串的依賴代碼,因此,我們可以 使用遍歷的方式去進(jìn)行依賴,其模板代碼如下所示:


// 在各個(gè) moulde 下的 build.gradle 腳本下
def implementationDependencies = rootProject.ext.dependencies
def processors = rootProject.ext.annotationProcessor
def apiFileDependencies = rootProject.ext.apiFileDependencies

// 在各個(gè) moulde 下的 build.gradle 腳本的 dependencies 閉包中
// 處理所有的 aar 依賴
apiFileDependencies.each { k, v -> api files(v)}

// 處理所有的 xxximplementation 依賴
implementationDependencies.each { k, v -> implementation v }
debugImplementationDependencies.each { k, v -> debugImplementation v } 
...

// 處理 annotationProcessor 依賴
processors.each { k, v -> annotationProcessor v }

// 處理所有包含 exclude 的依賴
debugImplementationExcludes.each { entry ->
    debugImplementation(entry.key) {
        entry.value.each { childEntry ->
            exclude(group: childEntry.key, module: childEntry.value)
        }
    }
}
復(fù)制代碼

也許未來(lái)隨著 Gradle 的不斷優(yōu)化會(huì)有更加簡(jiǎn)潔的方式,如果你有更好地方式,我們可以來(lái)探討一番。

在 gradle.properties 下定義擴(kuò)展屬性

除了使用 ext 擴(kuò)展屬性定義額外的屬性之外,我們也可以在 gradle.properties 下定義擴(kuò)展屬性,其示例代碼如下所示:

// 在 gradle.properties 中
mCompileVersion = 27

// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()
復(fù)制代碼

4、文件相關(guān) API

在 gradle 中,文件相關(guān)的 API 可以總結(jié)為如下 兩大類

  • 1)、路徑獲取 API
    • getRootDir()
    • getProjectDir()
    • getBuildDir()
  • 2)、文件操作相關(guān) API
    • 文件定位
    • 文件拷貝
    • 文件樹遍歷

1)、路徑獲取 API

關(guān)于路徑獲取的 API 常用的有 三種,其示例代碼如下所示:

/**
 * 1、路徑獲取 API
 */
println "the root file path is:" + getRootDir().absolutePath
println "this build file path is:" + getBuildDir().absolutePath
println "this Project file path is:" + getProjectDir().absolutePath
復(fù)制代碼

然后,我們執(zhí)行 ./gradlew clean,輸出結(jié)果如下所示:

> Configure project :
the root file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
this build file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid/build
this Project file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
配置階段,root project 'Awesome-WanAndroid'耗時(shí):538ms
復(fù)制代碼

2)、文件操作相關(guān) API

1、文件定位

常用的文件定位 API 有 file/files,其示例代碼如下所示:

// 在 rootProject 下的 build.gradle 中

/**
 * 1、文件定位之 file
 */
this.getContent("config.gradle")

def getContent(String path) {
    try {
        // 不同與 new file 的需要傳入 絕對(duì)路徑 的方式,
        // file 從相對(duì)于當(dāng)前的 project 工程開始查找
        def mFile = file(path)
        println mFile.text 
    } catch (GradleException e) {
        println e.toString()
        return null
    }
}

/**
 * 1、文件定位之 files
 */
this.getContent("config.gradle", "build.gradle")

def getContent(String path1, String path2) {
    try {
        // 不同與 new file 的需要傳入 絕對(duì)路徑 的方式,
        // file 從相對(duì)于當(dāng)前的 project 工程開始查找
        def mFiles = files(path1, path2)
        println mFiles[0].text + mFiles[1].text
    } catch (GradleException e) {
        println e.toString()
        return null
    }
}
復(fù)制代碼

2、文件拷貝

常用的文件拷貝 API 為 copy,其示例代碼如下所示:

/**
 * 2、文件拷貝
 */
copy {
    // 既可以拷貝文件,也可以拷貝文件夾
    // 這里是將 app moudle 下生成的 apk 目錄拷貝到
    // 根工程下的 build 目錄
    from file("build/outputs/apk")
    into getRootProject().getBuildDir().path + "/apk/"
    exclude {
        // 排除不需要拷貝的文件
    }
    rename {
        // 對(duì)拷貝過(guò)來(lái)的文件進(jìn)行重命名
    }
}
復(fù)制代碼

3、文件樹遍歷

我們可以 使用 fileTree 將當(dāng)前目錄轉(zhuǎn)換為文件數(shù)的形式,然后便可以獲取到每一個(gè)樹元素(節(jié)點(diǎn))進(jìn)行相應(yīng)的操作,其示例代碼如下所示:

/**
 * 3、文件樹遍歷
 */
fileTree("build/outputs/apk") { FileTree fileTree ->
    fileTree.visit { FileTreeElement fileTreeElement ->
        println "The file is $fileTreeElement.file.name"
        copy {
            from fileTreeElement.file
            into getRootProject().getBuildDir().path + "/apkTree/"
        }
    }
}
復(fù)制代碼

5、其它 API

1、依賴相關(guān) API

根項(xiàng)目下的 buildscript

buildscript 中 用于配置項(xiàng)目核心的依賴。其原始的使用示例與簡(jiǎn)化后的使用示例分別如下所示:

原始的使用示例
buildscript { ScriptHandler scriptHandler ->
    // 配置我們工程的倉(cāng)庫(kù)地址
    scriptHandler.repositories { RepositoryHandler repositoryHandler ->
        repositoryHandler.google()
        repositoryHandler.jcenter()
        repositoryHandler.mavenCentral()
        repositoryHandler.maven { url 'https://maven.google.com' }
        repositoryHandler.maven { url "https://plugins.gradle.org/m2/" }
        repositoryHandler.maven {
            url uri('../PAGradlePlugin/repo')
        }
        // 訪問(wèn)本地私有 Maven 服務(wù)器
        repositoryHandler.maven {
            name "personal"
            url "http://localhost:8081:/JsonChao/repositories"
            credentials {
                username = "JsonChao"
                password = "123456"
            }
        }
    }

      // 配置我們工程的插件依賴
    dependencies { DependencyHandler dependencyHandler ->
        dependencyHandler.classpath 'com.android.tools.build:gradle:3.1.4'

        ...
    }
復(fù)制代碼
簡(jiǎn)化后的使用示例
buildscript {
    // 配置我們工程的倉(cāng)庫(kù)地址
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'https://maven.google.com' }
        maven { url "https://plugins.gradle.org/m2/" }
        maven {
            url uri('../PAGradlePlugin/repo')
        }
    }

    // 配置我們工程的插件依賴
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'

        ...
    }
復(fù)制代碼

app moudle 下的 dependencies

不同于 根項(xiàng)目 buildscript 中的 dependencies 是用來(lái)配置我們 Gradle 工程的插件依賴的,而 app moudle 下的 dependencies 是用來(lái)為應(yīng)用程序添加第三方依賴的。關(guān)于 app moudle 下的依賴使用這里我們 需要注意下 exclude 與 transitive 的使用 即可,示例代碼如下所示:

implementation(rootProject.ext.dependencies.glide) {
        // 排除依賴:一般用于解決資源、代碼沖突相關(guān)的問(wèn)題
        exclude module: 'support-v4' 
        // 傳遞依賴:A => B => C ,B 中使用到了 C 中的依賴,
        // 且 A 依賴于 B,如果打開傳遞依賴,則 A 能使用到 B 
        // 中所使用的 C 中的依賴,默認(rèn)都是不打開,即 false
        transitive false 
}
復(fù)制代碼

2、外部命令執(zhí)行

我們一般是 使用 Gradle 提供的 exec 來(lái)執(zhí)行外部命令,下面我們就使用 exec 命令來(lái) 將當(dāng)前工程下新生產(chǎn)的 APK 文件拷貝到 電腦下的 Downloads 目錄中,示例代碼如下所示:

/**
 * 使用 exec 執(zhí)行外部命令
 */
task apkMove() {
    doLast { 
        // 在 gradle 的執(zhí)行階段去執(zhí)行
        def sourcePath = this.buildDir.path + "/outputs/apk/speed/release/"
        def destinationPath = "/Users/quchao/Downloads/"
        def command = "mv -f $sourcePath $destinationPath"
        exec {
            try {
                executable "bash"
                args "-c", command
                println "The command execute is success"
            } catch (GradleException e) {
                println "The command execute is failed"
            }
        }
    }
}
復(fù)制代碼

四、Task

只有 Task 才可以在 Gradle 的執(zhí)行階段去執(zhí)行(其實(shí)質(zhì)是執(zhí)行的 Task 中的一系列 Action),所以 Task 的重要性不言而喻。

1、從一個(gè)例子 ?? 出發(fā)

首先,我們可以在任意一個(gè) build.gradle 文件中可以去定義一個(gè) Task,下面是一個(gè)完整的示例代碼:

// 1、聲明一個(gè)名為 JsonChao 的 gradle task
task JsonChao
JsonChao {
    // 2、在 JsonChao task 閉包內(nèi)輸出 hello~,
    // 執(zhí)行在 gradle 生命周期的第二個(gè)階段,即配置階段。
    println("hello~")
    // 3、給 task 附帶一些 執(zhí)行動(dòng)作(Action),執(zhí)行在
    // gradle 生命周期的第三個(gè)階段,即執(zhí)行階段。
    doFirst {
        println("start")
    }
    doLast {
        println("end")
    }
}
// 4、除了上述這種將聲明與配置、Action 分別定義
// 的方式之外,也可以直接將它們結(jié)合起來(lái)。
// 這里我們又定義了一個(gè) Android task,它依賴于 JsonChao
// task,也就是說(shuō),必須先執(zhí)行完 JsonChao task,才能
// 去執(zhí)行 Android task,由此,它們之間便組成了一個(gè)
// 有向無(wú)環(huán)圖:JsonChao task => Android task
task Andorid(dependsOn:"JsonChao") {
    doLast {
        println("end?")
    }
}
復(fù)制代碼

首先,在注釋1處,我們聲明了一個(gè)名為 JsonChao 的 gradle task。接著,在注釋2處,在 JsonChao task 閉包內(nèi)輸出了 hello~,這里的代碼將會(huì)執(zhí)行在 gradle 生命周期的第二個(gè)階段,即配置階段。然后,在注釋3處,這里 給 task 附帶一些了一些執(zhí)行動(dòng)作(Action),即 doFirst 與 doLast,它們閉包內(nèi)的代碼將執(zhí)行在 gradle 生命周期的第三個(gè)階段,即執(zhí)行階段

對(duì)于 doFirst 與 doLast 這兩個(gè) Action,它們的作用分別如下所示:

  • doFirst表示 task 執(zhí)行最開始的時(shí)候被調(diào)用的 Action
  • doLast表示 task 將執(zhí)行完的時(shí)候被調(diào)用的 Action。

需要注意的是,doFirst 和 doLast 是可以被執(zhí)行多次的

最后,注釋4處,我們可以看到,除了注釋1、2、3處這種將聲明與配置、Action 分別定義的方式之外,也可以直接將它們結(jié)合起來(lái)。在這里我們又定義了一個(gè) Android task,它依賴于 JsonChao task,也就是說(shuō),必須先執(zhí)行完 JsonChao task,才能 去執(zhí)行 Android task,由此,它們之間便組成了一個(gè) 有向無(wú)環(huán)圖:JsonChao task => Android task。

執(zhí)行 Android 這個(gè) gradle task 可以看到如下輸出結(jié)果:

> Task :JsonChao
start
end
執(zhí)行階段,task ':JsonChao'耗時(shí):1ms
:JsonChao spend 4ms
> Task :Andorid
end?
執(zhí)行階段,task ':Andorid'耗時(shí):1ms
:Andorid spend 2ms
構(gòu)建結(jié)束 
Tasks spend time > 50ms:
執(zhí)行階段,耗時(shí):15ms
復(fù)制代碼

2、Task 的定義及配置

Task 常見的定義方式有 兩種,示例代碼如下所示:

// Task 定義方式1:直接通過(guò) task 函數(shù)去創(chuàng)建(在 "()" 可以不指定 group 與 description 屬性)
task myTask1(group: "MyTask", description: "task1") {
    println "This is myTask1"
}

// Task 定義方式2:通過(guò) TaskContainer 去創(chuàng)建 task
this.tasks.create(name: "myTask2") {
    setGroup("MyTask")
    setDescription("task2")
    println "This is myTask2"
}
復(fù)制代碼

定義完上述 Task 之后再同步項(xiàng)目,即可看到對(duì)應(yīng)的 Task Group 及其旗下的 Tasks。

Task 的屬性

需要注意的是,不管是哪一種 task 的定義方式,在 "()" 內(nèi)我們都可以配置它的一系列屬性,如下:

project.task('JsonChao3', group: "JsonChao", description: "my tasks",
dependsOn: ["JsonChao1", "JsonChao2"] ).doLast {
    println "execute JsonChao3 Task"
}
復(fù)制代碼

目前 官方所支持的屬性 可以總結(jié)為如下表格:

選型 描述 默認(rèn)值
"name" task 名字 無(wú),必須指定
"type" 需要?jiǎng)?chuàng)建的 task Class DefaultTask
"action" 當(dāng) task 執(zhí)行的時(shí)候,需要執(zhí)行的閉包 closure 或 行為 Action null
"overwrite" 替換一個(gè)已存在的 task false
"dependsOn" 該 task 所依賴的 task 集合 []
"group" 該 task 所屬組 null
"description" task 的描述信息 null
"constructorArgs" 傳遞到 task Class 構(gòu)造器中的參數(shù) null

使用 "$" 來(lái)引用另一個(gè) task 的屬性

在這里,我們可以 在當(dāng)前 task 中使用 "$" 來(lái)引用另一個(gè) task 的屬性,示例代碼如下所示:

task Gradle_First() {

}

task Gradle_Last() {
    doLast {
        println "I am not $Gradle_First.name"
    }
}
復(fù)制代碼

使用 ext 給 task 自定義需要的屬性

當(dāng)然,除了使用已有的屬性之外,我們也可以 使用 ext 給 task 自定義需要的屬性,代碼如下所示:

task Gradle_First() {
    ext.good = true
}

task Gradle_Last() {
    doFirst {
        println Gradle_First.good
    }
    doLast {
        println "I am not $Gradle_First.name"
    }
}
復(fù)制代碼

使用 defaultTasks 關(guān)鍵字標(biāo)識(shí)默認(rèn)執(zhí)行任務(wù)

此外,我們也可以 使用 defaultTasks 關(guān)鍵字 來(lái)將一些任務(wù)標(biāo)識(shí)為默認(rèn)的執(zhí)行任務(wù),代碼如下所示:

defaultTasks "Gradle_First", "Gradle_Last"

task Gradle_First() {
    ext.good = true
}

task Gradle_Last() {
    doFirst {
        println Gradle_First.goodg
    }
    doLast {
        println "I am not $Gradle_First.name"
    }
}
復(fù)制代碼

注意事項(xiàng)

每個(gè) task 都會(huì)經(jīng)歷 初始化、配置、執(zhí)行 這一套完整的生命周期流程。

3、Task 的執(zhí)行詳解

Task 通常使用 doFirst 與 doLast 兩個(gè)方式用于在執(zhí)行期間進(jìn)行操作。其示例代碼如下所示:

// 使用 Task 在執(zhí)行階段進(jìn)行操作
task myTask3(group: "MyTask", description: "task3") {
    println "This is myTask3"
    doFirst {
        // 老二
        println "This group is 2"
    }

    doLast {
        // 老三
        println "This description is 3"
    }
}

// 也可以使用 taskName.doxxx 的方式添加執(zhí)行任務(wù)
myTask3.doFirst {
    // 這種方式的最先執(zhí)行 => 老大
    println "This group is 1"
}
復(fù)制代碼

Task 執(zhí)行實(shí)戰(zhàn)

接下來(lái),我們就使用 doFirst 與 doLast 來(lái)進(jìn)行一下實(shí)戰(zhàn),來(lái)實(shí)現(xiàn) 計(jì)算 build 執(zhí)行期間的耗時(shí),其完整代碼如下所示:

// Task 執(zhí)行實(shí)戰(zhàn):計(jì)算 build 執(zhí)行期間的耗時(shí)
def startBuildTime, endBuildTime
// 1、在 Gradle 配置階段完成之后進(jìn)行操作,
// 以此保證要執(zhí)行的 task 配置完畢
this.afterEvaluate { Project project ->
    // 2、找到當(dāng)前 project 下第一個(gè)執(zhí)行的 task,即 preBuild task
    def preBuildTask = project.tasks.getByName("preBuild")
    preBuildTask.doFirst {
        // 3、獲取第一個(gè) task 開始執(zhí)行時(shí)刻的時(shí)間戳
        startBuildTime = System.currentTimeMillis()
    }
    // 4、找到當(dāng)前 project 下最后一個(gè)執(zhí)行的 task,即 build task
    def buildTask = project.tasks.getByName("build")
    buildTask.doLast {
        // 5、獲取最后一個(gè) task 執(zhí)行完成前一瞬間的時(shí)間戳
        endBuildTime = System.currentTimeMillis()
        // 6、輸出 build 執(zhí)行期間的耗時(shí)
        println "Current project execute time is ${endBuildTime - startBuildTime}"
    }
}
復(fù)制代碼

4、Task 的依賴和執(zhí)行順序

指定 Task 的執(zhí)行順序有 三種 方式,如下圖所示:

1)、dependsOn 強(qiáng)依賴方式

dependsOn 強(qiáng)依賴的方式可以細(xì)分為 靜態(tài)依賴和動(dòng)態(tài)依賴,示例代碼如下所示:

靜態(tài)依賴

task task1 {
    doLast {
        println "This is task1"
    }
}

task task2 {
    doLast {
        println "This is task2"
    }
}

// Task 靜態(tài)依賴方式1 (常用)
task task3(dependsOn: [task1, task2]) {
    doLast {
        println "This is task3"
    }
}

// Task 靜態(tài)依賴方式2
task3.dependsOn(task1, task2)
復(fù)制代碼

動(dòng)態(tài)依賴

// Task 動(dòng)態(tài)依賴方式
task dytask4 {
    dependsOn this.tasks.findAll { task ->
        return task.name.startsWith("task")
    }
    doLast {
        println "This is task4"
    }
}
復(fù)制代碼

2)、通過(guò) Task 指定輸入輸出

我們也可以通過(guò) Task 來(lái)指定輸入輸出,使用這種方式我們可以 高效地實(shí)現(xiàn)一個(gè) 自動(dòng)維護(hù)版本發(fā)布文檔的 gradle 腳本,其中輸入輸出相關(guān)的代碼如下所示:

task writeTask {
  inputs.property('versionCode', this.versionCode)
  inputs.property('versionName', this.versionName)
  inputs.property('versionInfo', this.versionInfo)
  // 1、指定輸出文件為 destFile
  outputs.file this.destFile
  doLast {
    //將輸入的內(nèi)容寫入到輸出文件中去
    def data = inputs.getProperties()
    File file = outputs.getFiles().getSingleFile()

    // 寫入版本信息到 XML 文件
    ...

}

task readTask {
  // 2、指定輸入文件為上一個(gè) task(writeTask) 的輸出文件 destFile
  inputs.file this.destFile
  doLast {
    //讀取輸入文件的內(nèi)容并顯示
    def file = inputs.files.singleFile
    println file.text
  }
}

task outputwithinputTask {
  // 3、先執(zhí)行寫入,再執(zhí)行讀取
  dependsOn writeTask, readTask
  doLast {
    println '輸入輸出任務(wù)結(jié)束'
  }
}
復(fù)制代碼

首先,我們定義了一個(gè) WirteTask,然后,在注釋1處,指定了輸出文件為 destFile, 并寫入版本信息到 XML 文件。接著,定義了一個(gè) readTask,并在注釋2處,指定輸入文件為上一個(gè) task(即 writeTask) 的輸出文件。最后,在注釋3處,使用 dependsOn 將這兩個(gè) task 關(guān)聯(lián)起來(lái),此時(shí)輸入與輸出的順序是會(huì)先執(zhí)行寫入,再執(zhí)行讀取。這樣,一個(gè)輸入輸出的實(shí)際案例就實(shí)現(xiàn)了。如果想要查看完整的實(shí)現(xiàn)代碼,請(qǐng)查看 Awesome-WanAndroid 的 releaseinfo.gradle 腳本

此外,在 McImage 中就利用了 dependsOn 的方式將自身的 task 插入到了 Gradle 的構(gòu)建流程之中,關(guān)鍵代碼如下所示:

// inject task
(project.tasks.findByName(chmodTask.name) as Task).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
復(fù)制代碼

通過(guò) API 指定依賴順序

除了 dependsOn 的方式,我們還可以在 task 閉包中通過(guò) mustRunAfter 方法指定 task 的依賴順序,需要注意的是,在最新的 gradle api 中,mustRunAfter 必須結(jié)合 dependsOn 強(qiáng)依賴進(jìn)行配套使用,其示例代碼如下所示:

// 通過(guò) API 指定依賴順序
task taskX {
    mustRunAfter "taskY"

    doFirst {
        println "this is taskX"
    }
}

task taskY {
    // 使用 mustRunAfter 指定依賴的(一至多個(gè))前置 task
    // 也可以使用 shouldRunAfter 的方式,但是是非強(qiáng)制的依賴
//    shouldRunAfter taskA
    doFirst {
        println "this is taskY"
    }
}

task taskZ(dependsOn: [taskX, taskY]) {
    mustRunAfter "taskY"
    doFirst {
        println "this is taskZ"
    }
}
復(fù)制代碼

5、Task 類型

除了定義一個(gè)新的 task 之外,我們也可以使用 type 屬性來(lái)直接使用一個(gè)已有的 task 類型(很多文章都說(shuō)的是繼承一個(gè)已有的類,不是很準(zhǔn)確),比如 Gradle 自帶的 Copy、Delete、Sync task 等等。示例代碼如下所示:

// 1、刪除根目錄下的 build 文件
task clean(type: Delete) {
    delete rootProject.buildDir
}
// 2、將 doc 復(fù)制到 build/target 目錄下
task copyDocs(type: Copy) {
    from 'src/main/doc'
    into 'build/target/doc'
}
// 3、執(zhí)行時(shí)會(huì)復(fù)制源文件到目標(biāo)目錄,然后從目標(biāo)目錄刪除所有非復(fù)制文件
task syncFile(type:Sync) {
    from 'src/main/doc'
    into 'build/target/doc'
}
復(fù)制代碼

6、掛接到構(gòu)建生命周期

我們可以使用 gradle 提供的一系列生命周期 API 去掛接我們自己的 task 到構(gòu)建生命周期之中,比如使用 afterEvaluate 方法 將我們第三小節(jié)定義的 writeTask 掛接到 gradle 配置完所有的 task 之后的時(shí)刻,示例代碼如下所示:

// 在配置階段執(zhí)行完之后執(zhí)行 writeTask
this.project.afterEvaluate { project ->
  def buildTask = project.tasks.findByName("build")
  doLast {
    buildTask.doLast {
      // 5.x 上使用 finalizedBy
      writeTask.execute()
    }
  }
}
復(fù)制代碼

需要注意的是,配置完成之后,我們需要在 app moudle 下引入我們定義的 releaseinfo 腳本,引入方式如下:

apply from: this.project.file("releaseinfo.gradle")
復(fù)制代碼

五、SourceSet

SourceSet 主要是 用來(lái)設(shè)置我們項(xiàng)目中源碼或資源的位置的,目前它最常見的兩個(gè)使用案例就是如下 兩類

  • 1)、修改 so 庫(kù)存放位置
  • 2)、資源文件分包存放

1、修改 so 庫(kù)存放位置

我們僅需在 app moudle 下的 android 閉包下配置如下代碼即可修改 so 庫(kù)存放位置:

android {
    ...
    sourceSets {
        main {
            // 修改 so 庫(kù)存放位置
            jniLibs.srcDirs = ["libs"]
        }
    }
}
復(fù)制代碼

2、資源文件分包存放

同樣,在 app moudle 下的 android 閉包下配置如下代碼即可將資源文件進(jìn)行分包存放:

android {
    sourceSets {
        main {
            res.srcDirs = ["src/main/res",
                           "src/main/res-play",
                           "src/main/res-shop"
                            ... 
                           ]
        }
    }
}
復(fù)制代碼

此外,我們也可以使用如下代碼 將 sourceSets 在 android 閉包的外部進(jìn)行定義

this.android.sourceSets {
    ...
}
復(fù)制代碼

六、Gradle 命令

Gradle 的命令有很多,但是我們通常只會(huì)使用如下兩種類型的命令:

  • 1)、獲取構(gòu)建信息的命令。
  • 2)、執(zhí)行 task 的命令

1、獲取構(gòu)建信息的命令

// 1、按自頂向下的結(jié)構(gòu)列出子項(xiàng)目的名稱列表
./gradlew projects
// 2、分類列出項(xiàng)目中所有的任務(wù)
./gradlew tasks
// 3、列出項(xiàng)目的依賴列表
./gradlew dependencies
復(fù)制代碼

2、執(zhí)行 task 的命令

常規(guī)的用于執(zhí)行 task 的命令有 四種,如下所示:

// 1、用于執(zhí)行多個(gè) task 任務(wù)
./gradlew JsonChao Gradle_Last
// 2、使用 -x 排除單個(gè) task 任務(wù)
./gradlew -x JsonChao
// 3、使用 -continue 可以在構(gòu)建失敗后繼續(xù)執(zhí)行下面的構(gòu)建命令
./gradlew -continue JsonChao
// 4、建議使用簡(jiǎn)化的 task name 去執(zhí)行 task,下面的命令用于執(zhí)行 
// Gradle_Last 這個(gè) task
./gradlew G_Last
復(fù)制代碼

而對(duì)于子目錄下定義的 task,我們通常會(huì)使用如下的命令來(lái)執(zhí)行它:

// 1、使用 -b 執(zhí)行 app 目錄下定義的 task
./gradlew -b app/build.gradle MyTask
// 2、在大型項(xiàng)目中我們一般使用更加智能的 -p 來(lái)替代 -b
./gradlew -p app MyTask
復(fù)制代碼

七、總結(jié)

至此,我們就將 Gradle 的核心 API 部分講解完畢了,這里我們?cè)賮?lái)回顧一下本文的要點(diǎn),如下所示:

  • 一、Gradle 優(yōu)勢(shì)
    • 1、更好的靈活性
    • 2、更細(xì)的粒度
    • 3、更好的擴(kuò)展性
    • 4、更強(qiáng)的兼容性
  • 二、Gradle 構(gòu)建生命周期
    • 1、初始化階段
    • 2、配置階段
    • 3、執(zhí)行階段
    • 4、Hook Gradle 各個(gè)生命周期節(jié)點(diǎn)
    • 5、獲取構(gòu)建各個(gè)階段、任務(wù)的耗時(shí)情況
  • 三、Project
    • 1、Project 核心 API 分解
    • 2、Project API
    • 3、project 屬性
    • 4、文件相關(guān) API
    • 5、其它 API
  • 四、Task
    • 1、從一個(gè)例子 ?? 出發(fā)
    • 2、Task 的定義及配置
    • 3、Task 的執(zhí)行詳解
    • 4、Task 的依賴和執(zhí)行順序
    • 5、Task 類型
    • 6、掛接到構(gòu)建生命周期
  • 五、SourceSet
    • 1、修改 so 庫(kù)存放位置
    • 2、資源文件分包存放
  • 六、Gradle 命令
    • 1、獲取構(gòu)建信息的命令
    • 2、執(zhí)行 task 的命令

Gradle 的核心 API 非常重要,這對(duì)我們高效實(shí)現(xiàn)一個(gè) Gradle 插件無(wú)疑是必不可少的。因?yàn)?只有扎實(shí)基礎(chǔ)才能走的更遠(yuǎn),愿我們能一同前行。

作者:jsonchao
鏈接:https://juejin.cn/post/6844904132092903437
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容