成為一名優(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)的 Api:Gradle 會(huì)預(yù)先為我們提供一些 Project 屬性,而屬性相關(guān)的 api 讓我們擁有了為 Project 添加額外屬性的能力。 - 4)、
File 相關(guān) Api:Project 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)注明出處。