從明面上看,Gradle 是一款強大的構建工具,並且許多文章也僅僅都把 Gradle 當作一款工具對待。可是,Gradle 不只僅是一款強大的構建工具,它看起來更像是一個編程框架。Gradle 的組成能夠細分爲以下三個方面:html
groovy 核心語法
:
包括 groovy 基本語法、閉包、數據結構、面向對象等等。
Android DSL(build scrpit block)
:
Android 插件在 Gradle 所特有的東西,咱們能夠在不一樣的 build scrpit block 中去作不一樣的事情。
Gradle API
:
包含 Project、Task、Setting 等等(本文重點)。
能夠看到,Gradle 的語法是以 groovy 爲基礎的,並且,它還有本身獨有的 API,因此咱們能夠把 Gradle 認做是一款編程框架,利用 Gradle 咱們能夠在編程中去實現項目構建過程當中的全部需求。須要注意的是,想要爲所欲爲地使用 Gradle,咱們必須提早掌握好 groovy,若是對 groovy 還不是很熟悉的建議看看 《深刻探索Gradle自動化構建技術(2、Groovy 築基篇)》 一文。java
須要注意的是,Groovy 是一門語言,而 DSL 一種特定領域的配置文件,Gradle 是基於 Groovy 的一種框架工具,而 gradlew 則是 gradle 的一個兼容包裝工具。android
在靈活性上,Gradle 相對於 Maven、Ant 等構建工具, 其 提供了一系列的 API 讓咱們有能力去修改或定製項目的構建過程。例如咱們能夠 利用 Gradle 去動態修改生成的 APK 包名,可是若是是使用的 Maven、Ant 等工具,咱們就必須等生成 APK 後,再手動去修改 APK 的名稱。git
在粒度性上,使用 Maven、Ant 等構建工具時,咱們的源代碼和構建腳本是獨立的,並且咱們也不知道其內部的處理是怎樣的。可是,咱們的 Gradle 則不一樣,它 從源代碼的編譯、資源的編譯、再到生成 APK 的過程當中都是一個接一個來執行的。github
此外,Gradle 構建的粒度細化到了每個 task 之中。而且它全部的 Task 源碼都是開源的,在咱們掌握了這一整套打包流程後,咱們就能夠經過去修改它的 Task 去動態改變其執行流程。例如 Tinker 框架的實現過程當中,它經過動態地修改 Gradle 的打包過程生成 APK 的同時,也生成了各類補丁文件。web
在擴展性上,Gradle 支持插件機制,因此咱們能夠複用這些插件,就如同複用庫同樣簡單方便。編程
Gradle 不只自身功能強大,並且它還能 兼容全部的 Maven、Ant 功能,也就是說,Gradle 吸收了全部構建工具的長處。json
能夠看到,Gradle 相比於其它構建工具,其好處不言而喻,而其 最核心的緣由就是由於 Gradle 是一套編程框架。api
Gradle 的構建過程分爲 三部分:初始化階段、配置階段和執行階段。其構建流程以下圖所示:數組
下面分別來詳細瞭解下它們。
首先,在這個階段中,會讀取根工程中的 setting.gradle 中的 include 信息,肯定有多少工程加入構建,而後,會爲每個項目(build.gradle 腳本文件)建立一個個與之對應的 Project 實例,最終造成一個項目的層次結構。 與初始化階段相關的腳本文件是 settings.gradle,而一個 settings.gradle 腳本對應一個 Settings 對象,咱們最經常使用來聲明項目的層次結構的 include 就是 Settings 對象下的一個方法,在 Gradle 初始化的時候會構造一個 Settings 實例對象,以執行各個 Project 的初始化配置。
在 settings.gradle 文件中,咱們能夠 在 Gradle 的構建過程當中添加各個生命週期節點監聽,其代碼以下所示:
include ':app'
gradle.addBuildListener(new BuildListener() {
void buildStarted(Gradle var1) {
println '開始構建'
}
void settingsEvaluated(Settings var1) {
// var1.gradle.rootProject 這裏訪問 Project 對象時會報錯,
// 由於還未完成 Project 的初始化。
println 'settings 評估完成(settings.gradle 中代碼執行完畢)'
}
void projectsLoaded(Gradle var1) {
println '項目結構加載完成(初始化階段結束)'
println '初始化結束,可訪問根項目:' + var1.gradle.rootProject
}
void projectsEvaluated(Gradle var1) {
println '全部項目評估完成(配置階段結束)'
}
void buildFinished(BuildResult var1) {
println '構建結束 '
}
})
複製代碼
編寫完相應的 Gradle 生命週期監聽代碼以後,咱們就能夠在 Build 輸出界面看到以下信息:
Executing tasks: [clean, :app:assembleSpeedDebug] in project
/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
settings評估完成(settins.gradle中代碼執行完畢)
項目結構加載完成(初始化階段結束)
初始化結束,可訪問根項目: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!!!!!!
全部項目評估完成(配置階段結束)
> 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
構建結束
Tasks spend time > 50ms:
...
複製代碼
此外,在 settings.gradle 文件中,咱們能夠指定其它 project 的位置,這樣就能夠將其它外部工程中的 moudle 導入到當前的工程之中了。示例代碼以下所示:
if (useSpeechMoudle) {
// 導入其它 App 的 speech 語音模塊
include "speech"
project(":speech").projectDir = new File("../OtherApp/speech")
}
複製代碼
配置階段的任務是 執行各項目下的 build.gradle 腳本,完成 Project 的配置,與此同時,會構造 Task 任務依賴關係圖以便在執行階段按照依賴關係執行 Task。而在配置階段執行的代碼一般來講都會包括如下三個部分的內容,以下所示:
須要注意的是,執行任何 Gradle 命令,在初始化階段和配置階段的代碼都會被執行。
在配置階段結束後,Gradle 會根據各個任務 Task 的依賴關係來建立一個有向無環圖,咱們能夠經過 Gradle 對象的 getTaskGraph 方法來獲得該有向無環圖 => TaskExecutionGraph,而且,當有向無環圖構建完成以後,全部 Task 執行以前,咱們能夠經過 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 來接收相應的通知,其代碼以下所示:
gradle.getTaskGraph().addTaskExecutionGraphListener(new
TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
}
})
複製代碼
而後,Gradle 構建系統會經過調用 gradle <任務名> 來執行相應的各個任務。
這裏借用 Goe_H 的 Gradle 生命週期時序圖來說解一下 Gradle 生命週期的整個流程,以下圖所示:
能夠看到,整個 Gradle 生命週期的流程包含以下 四個部分:
在 Gradle 構建命令中,最爲複雜的命令能夠說是 gradle build
這個命令了,由於項目的構建過程當中須要依賴不少其它的 task。這裏,咱們以 Java 項目的構建過程看看它所依賴的 tasks 及其組成的有向無環圖,以下所示:
瞭解了 Gradle 生命週期中的各個 Hook 方法以後,咱們就能夠 利用它們來獲取項目構建各個階段、任務的耗時狀況,在 settings.gradle 中加入以下代碼便可:
long beginOfSetting = System.currentTimeMillis()
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
def beginOfProjectExcute
gradle.projectsLoaded {
println '初始化階段,耗時:' + (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 + '耗時:' +
(System.currentTimeMillis() - begin) + 'ms'
}
gradle.taskGraph.whenReady {
println '配置階段,總共耗時:' + (System.currentTimeMillis() -
beginOfConfig) + 'ms'
beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println '執行階段,' + task + '耗時:' +
(System.currentTimeMillis() - task.beginOfTask) + 'ms'
}
}
gradle.buildFinished {
println '執行階段,耗時:' + (System.currentTimeMillis() -
beginOfProjectExcute) + 'ms'
}
複製代碼
在 Gradle 中,執行每一種類型的配置腳本就會建立與之對應的實例,而在 Gradle 中如 三種類型的配置腳本,以下所示:
Build Scrpit
:
對應一個 Project 實例,即每一個 build.gradle 都會轉換成一個 Project 實例。
Init Scrpit
:
對應一個 Gradle 實例,它在構建初始化時建立,整個構建執行過程當中以單例形式存在。
Settings Scrpit
:
對應一個 Settings 實例,即每一個 settings.gradle 都會轉換成一個 Settings 實例。
能夠看到,一個 Gradle 構建流程中會由一至多個 project 實例構成,而每個 project 實例又是由一至多個 task 構成。下面,咱們就來認識下 Project。
Project 是 Gradle 構建整個應用程序的入口,因此它很是重要,咱們必須對其有深入地瞭解。不幸的是,網上幾乎沒有關於 project 講解的比較好的文章,不過不要緊,下面,咱們將會一塊兒來深刻學習 project api 這部分。
由前可知,每個 build.gradle 都有一個與之對應的 Project 實例,而在 build.gradle 中,咱們一般都會配置一系列的項目依賴,以下面這個依賴:
implementation 'com.github.bumptech.glide:glide:4.8.0'
複製代碼
相似於 implementation、api 這種依賴關鍵字,在本質上它就是一個方法調用,在上面,咱們使用 implementation() 方法傳入了一個 map 參數,參數裏面有三對 key-value,完整寫法以下所示:
implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0'
複製代碼
當咱們使用 implementation、api 依賴對應的 aar 文件時,Gradle 會在 repository 倉庫 裏面找到與之對應的依賴文件,你的倉庫中可能包含 jcenter、maven 等一系列倉庫,而每個倉庫其實就是不少依賴文件的集合服務器, 而他們就是經過上述的 group、name、version 來進行歸類存儲的。
在 Project 中有不少的 API,可是根據它們的 屬性和用途 咱們能夠將其分解爲 六大部分,以下圖所示:
對於 Project 中各個部分的做用,咱們能夠先來大體瞭解下,以便爲 Project 的 API 體系創建一個總體的感知能力,以下所示:
Project API
:
讓當前的 Project 擁有了操做它的父 Project 以及管理它的子 Project 的能力。
Task 相關 API
:
爲當前 Project 提供了新增 Task 以及管理已有 Task 的能力。因爲 task 很是重要,咱們將放到第四章來進行講解。
Project 屬性相關的 Api
:
Gradle 會預先爲咱們提供一些 Project 屬性,而屬性相關的 api 讓咱們擁有了爲 Project 添加額外屬性的能力。
File 相關 Api
:
Project File 相關的 API 主要用來操做咱們當前 Project 下的一些文件處理。
Gradle 生命週期 API
:
即咱們在第二章講解過的生命週期 API。
其它 API
:
添加依賴、添加配置、引入外部文件等等零散 API 的聚合。
每個 Groovy 腳本都會被編譯器編譯成 Script 字節碼,而每個 build.gradle 腳本都會被編譯器編譯成 Project 字節碼,因此咱們在 build.gradle 中所寫的一切邏輯都是在 Project 類內進行書寫的。下面,咱們將按照由易到難的套路來介紹 Project 的一系列重要的 API。
須要提早說明的是,默認狀況下咱們選定根工程的 build.gradle 這個腳本文件中來學習 Project 的一系列用法,關於 getAllProject 的用法以下所示:
getAllprojects 表示 獲取全部 project 的實例,示例代碼以下所示:
/**
* getAllProjects 使用示例
*/
this.getProjects()
def getProjects() {
println "<================>"
println " Root Project Start "
println "<================>"
// 一、getAllprojects 方法返回一個包含根 project 與其子 project 的 Set 集合
// eachWithIndex 方法用於遍歷集合、數組等可迭代的容器,
// 並同時返回下標,不一樣於 each 方法僅返回 project
this.getAllprojects().eachWithIndex { Project project, int index ->
// 二、下標爲 0,代表當前遍歷的是 rootProject
if (index == 0) {
println "Root Project is $project"
} else {
println "child Project is $project"
}
}
}
複製代碼
首先,咱們使用了 def 關鍵字定義了一個 getProjects 方法。而後,在註釋1處,咱們調用了 getAllprojects 方法返回一個包含根 project 與其子 project 的 Set 集合,並鏈式調用了 eachWithIndex 遍歷 Set 集合。接着,在註釋2處,咱們會判斷當前的下標 index 是不是0,若是是,則代表當前遍歷的是 rootProject,則輸出 rootProject 的名字,不然,輸出 child project 的名字。
下面,咱們在命令行執行 ./gradlew clean
,其運行結果以下所示:
quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評估完成(settings.gradle 中代碼執行完畢)
項目結構加載完成(初始化階段結束)
初始化結束,可訪問根項目:root project 'Awesome-WanAndroid'
初始化階段,耗時: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'耗時:284ms
> Configure project :app
...
配置階段,總共耗時:428ms
> Task :app:clean
執行階段,task ':app:clean'耗時:1ms
:app:clean spend 2ms
構建結束
Tasks spend time > 50ms:
執行階段,耗時:9ms
複製代碼
能夠看到,執行了初始化以後,就會先配置咱們的 rootProject,並輸出了對應的工程信息。接着,便會執行子工程 app 的配置。最後,執行了 clean 這個 task。
須要注意的是,rootProject 與其旗下的各個子工程組成了一個樹形結構,可是這顆樹的高度也僅僅被限定爲了兩層。
getSubprojects 表示獲取當前工程下全部子工程的實例,示例代碼以下所示:
/**
* getAllsubproject 使用示例
*/
this.getSubProjects()
def getSubProjects() {
println "<================>"
println " Sub Project Start "
println "<================>"
// getSubprojects 方法返回一個包含子 project 的 Set 集合
this.getSubprojects().each { Project project ->
println "child Project is $project"
}
}
複製代碼
同 getAllprojects 的用法同樣,getSubprojects 方法返回了一個包含子 project 的 Set 集合,這裏咱們直接使用 each 方法將各個子 project 的名字打印出來。其運行結果以下所示:
quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評估完成(settings.gradle 中代碼執行完畢)
...
> Configure project :
<================>
Sub Project Start
<================>
child Project is project ':app'
配置階段,root project 'Awesome-WanAndroid'耗時:289ms
> Configure project :app
...
全部項目評估完成(配置階段結束)
配置階段,總共耗時:425ms
> Task :app:clean
執行階段,task ':app:clean'耗時:1ms
:app:clean spend 2ms
構建結束
Tasks spend time > 50ms:
執行階段,耗時:9ms
複製代碼
能夠看到,一樣在 Gradle 的配置階段輸出了子工程的名字。
getParent 表示 獲取當前 project 的父類,須要注意的是,若是咱們在根工程中使用它,獲取的父類會爲 null,由於根工程沒有父類,因此這裏咱們直接在 app 的 build.gradle 下編寫下面的示例代碼:
...
> Configure project :
配置階段,root project 'Awesome-WanAndroid'耗時:104ms
> Configure project :app
gradlew version > 4.0
my parent project is Awesome-WanAndroid
配置階段,project ':app'耗時:282ms
...
全部項目評估完成(配置階段結束)
配置階段,總共耗時:443ms
...
複製代碼
能夠看到,這裏輸出了 app project 當前的父類,即 Awesome-WanAndroid project。
若是咱們想在根工程僅僅獲取當前的 project 實例該怎麼辦呢?直接使用 getRootProject 便可在任意 build.gradle 文件獲取當前根工程的 project 實例,示例代碼以下所示:
/**
* 四、getRootProject 使用示例
*/
this.getRootPro()
def getRootPro() {
def rootProjectName = this.getRootProject().name
println "root project is $rootProjectName"
}
複製代碼
project 表示的是 指定工程的實例,而後在閉包中對其進行操做。在使用以前,咱們有必要看看 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);
複製代碼
能夠看到,在 project 方法中兩個參數,一個是指定工程的路徑,另外一個是用來配置該工程的閉包。下面咱們看看如何靈活地使用 project,示例代碼以下所示:
/**
* 五、project 使用示例
*/
// 一、閉包參數能夠放在括號外面
project("app") { Project project ->
apply plugin: 'com.android.application'
}
// 二、更簡潔的寫法是這樣的:省略參數
project("app") {
apply plugin: 'com.android.application'
}
複製代碼
使用熟練以後,咱們一般會採用註釋2處的寫法。
allprojects 表示 用於配置當前 project 及其旗下的每個子 project,以下所示:
/**
* 六、allprojects 使用示例
*/
// 同 project 同樣的更簡潔寫法
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven {
url "https://jitpack.io"
}
maven { url "https://plugins.gradle.org/m2/" }
}
}
複製代碼
在 allprojects 中咱們通常用來配置一些通用的配置,好比上面最多見的全局倉庫配置。
subprojects 能夠 統一配置當前 project 下的全部子 project,示例代碼以下所示:
/**
* 七、subprojects 使用示例:
* 給全部的子工程引入 將 aar 文件上傳置 Maven 服務器的配置腳本
*/
subprojects {
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
}
}
複製代碼
在上述示例代碼中,咱們會先判斷當前 project 旗下的子 project 是否是庫,若是是庫纔有必要引入 publishToMaven 腳本。
目前,在 project 接口裏,僅僅預先定義了 七個 屬性,其源碼以下所示:
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
/**
* 默認的工程構建文件名稱
*/
String DEFAULT_BUILD_FILE = "build.gradle";
/**
* 區分開 project 名字與 task 名字的符號
*/
String PATH_SEPARATOR = ":";
/**
* 默認的構建目錄名稱
*/
String DEFAULT_BUILD_DIR_NAME = "build";
String GRADLE_PROPERTIES = "gradle.properties";
String SYSTEM_PROP_PREFIX = "systemProp";
String DEFAULT_VERSION = "unspecified";
String DEFAULT_STATUS = "release";
...
}
複製代碼
幸運的是,Gradle 提供了 ext 關鍵字讓咱們有能力去定義自身所須要的擴展屬性。有了它即可以對咱們工程中的依賴進行全局配置。下面,咱們先從配置的遠古時代講起,以便讓咱們對 gradle 的 全局依賴配置有更深刻的理解。
在 AS 剛出現的時候,咱們的依賴配置代碼是這樣的:
android {
compileSdkVersion 27
buildToolsVersion "28.0.3"
...
}
複製代碼
可是這種直接寫值的方式顯示是不規範的,所以,後面咱們使用了這種方式:
def mCompileSdkVersion = 27
def mBuildToolsVersion = "28.0.3"
android {
compileSdkVersion mCompileSdkVersion
buildToolsVersion mBuildToolsVersion
...
}
複製代碼
若是每個子 project 都須要配置相同的 Version,咱們就須要多寫不少的重複代碼,所以,咱們能夠利用上面咱們學過的 subproject 和 ext 來進行簡化:
// 在根目錄下的 build.gradle 中
subprojects {
ext {
compileSdkVersion = 27
buildToolsVersion = "28.0.3"
}
}
// 在 app moudle 下的 build.gradle 中
android {
compileSdkVersion this.compileSdkVersion
buildToolsVersion this.buildToolsVersion
...
}
複製代碼
使用 subprojects 方法來定義通用的擴展屬性仍是存在着很嚴重的問題,它跟以前的方式同樣,仍是會在每個子 project 去定義這些被擴展的屬性,此時,咱們能夠將 subprojects 去除,直接使用 ext 進行全局定義便可:
// 在根目錄下的 build.gradle 中
ext {
compileSdkVersion = 27
buildToolsVersion = "28.0.3"
}
複製代碼
當項目愈來愈大的時候,在根項目下定義的 ext 擴展屬性愈來愈多,所以,咱們能夠將這一套全局屬性配置在另外一個 gradle 腳本中進行定義,這裏咱們一般會將其命名爲 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"
]
...
}
複製代碼
儘管有了很全面的全局依賴配置文件,可是,在咱們的各個模塊之中,仍是不得不寫一大長串的依賴代碼,所以,咱們能夠 使用遍歷的方式去進行依賴,其模板代碼以下所示:
// 在各個 moulde 下的 build.gradle 腳本下
def implementationDependencies = rootProject.ext.dependencies
def processors = rootProject.ext.annotationProcessor
def apiFileDependencies = rootProject.ext.apiFileDependencies
// 在各個 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)
}
}
}
複製代碼
也許將來隨着 Gradle 的不斷優化會有更加簡潔的方式,若是你有更好地方式,咱們能夠來探討一番。
除了使用 ext 擴展屬性定義額外的屬性以外,咱們也能夠在 gradle.properties 下定義擴展屬性,其示例代碼以下所示:
// 在 gradle.properties 中
mCompileVersion = 27
// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()
複製代碼
在 gradle 中,文件相關的 API 能夠總結爲以下 兩大類:
getRootDir()
getProjectDir()
getBuildDir()
文件定位
文件拷貝
文件樹遍歷
關於路徑獲取的 API 經常使用的有 三種,其示例代碼以下所示:
/**
* 一、路徑獲取 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
複製代碼
而後,咱們執行 ./gradlew clean
,輸出結果以下所示:
> 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'耗時:538ms
複製代碼
經常使用的文件定位 API 有 file/files
,其示例代碼以下所示:
// 在 rootProject 下的 build.gradle 中
/**
* 一、文件定位之 file
*/
this.getContent("config.gradle")
def getContent(String path) {
try {
// 不一樣與 new file 的須要傳入 絕對路徑 的方式,
// file 從相對於當前的 project 工程開始查找
def mFile = file(path)
println mFile.text
} catch (GradleException e) {
println e.toString()
return null
}
}
/**
* 一、文件定位之 files
*/
this.getContent("config.gradle", "build.gradle")
def getContent(String path1, String path2) {
try {
// 不一樣與 new file 的須要傳入 絕對路徑 的方式,
// file 從相對於當前的 project 工程開始查找
def mFiles = files(path1, path2)
println mFiles[0].text + mFiles[1].text
} catch (GradleException e) {
println e.toString()
return null
}
}
複製代碼
經常使用的文件拷貝 API 爲 copy
,其示例代碼以下所示:
/**
* 二、文件拷貝
*/
copy {
// 既能夠拷貝文件,也能夠拷貝文件夾
// 這裏是將 app moudle 下生成的 apk 目錄拷貝到
// 根工程下的 build 目錄
from file("build/outputs/apk")
into getRootProject().getBuildDir().path + "/apk/"
exclude {
// 排除不須要拷貝的文件
}
rename {
// 對拷貝過來的文件進行重命名
}
}
複製代碼
咱們能夠 使用 fileTree 將當前目錄轉換爲文件數的形式,而後即可以獲取到每個樹元素(節點)進行相應的操做,其示例代碼以下所示:
/**
* 三、文件樹遍歷
*/
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/"
}
}
}
複製代碼
buildscript 中 用於配置項目核心的依賴。其原始的使用示例與簡化後的使用示例分別以下所示:
buildscript { ScriptHandler scriptHandler ->
// 配置咱們工程的倉庫地址
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')
}
// 訪問本地私有 Maven 服務器
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'
...
}
複製代碼
buildscript {
// 配置咱們工程的倉庫地址
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'
...
}
複製代碼
不一樣於 根項目 buildscript 中的 dependencies 是用來配置咱們 Gradle 工程的插件依賴的,而 app moudle 下的 dependencies 是用來爲應用程序添加第三方依賴的。關於 app moudle 下的依賴使用這裏咱們 須要注意下 exclude 與 transitive 的使用 便可,示例代碼以下所示:
implementation(rootProject.ext.dependencies.glide) {
// 排除依賴:通常用於解決資源、代碼衝突相關的問題
exclude module: 'support-v4'
// 傳遞依賴:A => B => C ,B 中使用到了 C 中的依賴,
// 且 A 依賴於 B,若是打開傳遞依賴,則 A 能使用到 B
// 中所使用的 C 中的依賴,默認都是不打開,即 false
transitive false
}
複製代碼
咱們通常是 使用 Gradle 提供的 exec 來執行外部命令,下面咱們就使用 exec 命令來 將當前工程下新生產的 APK 文件拷貝到 電腦下的 Downloads 目錄中,示例代碼以下所示:
/**
* 使用 exec 執行外部命令
*/
task apkMove() {
doLast {
// 在 gradle 的執行階段去執行
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"
}
}
}
}
複製代碼
只有 Task 才能夠在 Gradle 的執行階段去執行(其實質是執行的 Task 中的一系列 Action),因此 Task 的重要性不言而喻。
首先,咱們能夠在任意一個 build.gradle 文件中能夠去定義一個 Task,下面是一個完整的示例代碼:
// 一、聲明一個名爲 JsonChao 的 gradle task
task JsonChao
JsonChao {
// 二、在 JsonChao task 閉包內輸出 hello~,
// 執行在 gradle 生命週期的第二個階段,即配置階段。
println("hello~")
// 三、給 task 附帶一些 執行動做(Action),執行在
// gradle 生命週期的第三個階段,即執行階段。
doFirst {
println("start")
}
doLast {
println("end")
}
}
// 四、除了上述這種將聲明與配置、Action 分別定義
// 的方式以外,也能夠直接將它們結合起來。
// 這裏咱們又定義了一個 Android task,它依賴於 JsonChao
// task,也就是說,必須先執行完 JsonChao task,才能
// 去執行 Android task,由此,它們之間便組成了一個
// 有向無環圖:JsonChao task => Android task
task Andorid(dependsOn:"JsonChao") {
doLast {
println("end?")
}
}
複製代碼
首先,在註釋1處,咱們聲明瞭一個名爲 JsonChao 的 gradle task。接着,在註釋2處,在 JsonChao task 閉包內輸出了 hello~,這裏的代碼將會執行在 gradle 生命週期的第二個階段,即配置階段。而後,在註釋3處,這裏 給 task 附帶一些了一些執行動做(Action),即 doFirst 與 doLast,它們閉包內的代碼將執行在 gradle 生命週期的第三個階段,即執行階段。
對於 doFirst 與 doLast 這兩個 Action,它們的做用分別以下所示:
doFirst
:
表示 task 執行最開始的時候被調用的 Action。
doLast
:
表示 task 將執行完的時候被調用的 Action。
須要注意的是,doFirst 和 doLast 是能夠被執行屢次的。
最後,註釋4處,咱們能夠看到,除了註釋一、二、3處這種將聲明與配置、Action 分別定義的方式以外,也能夠直接將它們結合起來。在這裏咱們又定義了一個 Android task,它依賴於 JsonChao task,也就是說,必須先執行完 JsonChao task,才能 去執行 Android task,由此,它們之間便組成了一個 有向無環圖:JsonChao task => Android task。
執行 Android 這個 gradle task 能夠看到以下輸出結果:
> Task :JsonChao
start
end
執行階段,task ':JsonChao'耗時:1ms
:JsonChao spend 4ms
> Task :Andorid
end?
執行階段,task ':Andorid'耗時:1ms
:Andorid spend 2ms
構建結束
Tasks spend time > 50ms:
執行階段,耗時:15ms
複製代碼
Task 常見的定義方式有 兩種,示例代碼以下所示:
// Task 定義方式1:直接經過 task 函數去建立(在 "()" 能夠不指定 group 與 description 屬性)
task myTask1(group: "MyTask", description: "task1") {
println "This is myTask1"
}
// Task 定義方式2:經過 TaskContainer 去建立 task
this.tasks.create(name: "myTask2") {
setGroup("MyTask")
setDescription("task2")
println "This is myTask2"
}
複製代碼
定義完上述 Task 以後再同步項目,便可看到對應的 Task Group 及其旗下的 Tasks,以下圖所示:
須要注意的是,無論是哪種 task 的定義方式,在 "()" 內咱們均可以配置它的一系列屬性,以下:
project.task('JsonChao3', group: "JsonChao", description: "my tasks",
dependsOn: ["JsonChao1", "JsonChao2"] ).doLast {
println "execute JsonChao3 Task"
}
複製代碼
目前 官方所支持的屬性 能夠總結爲以下表格:
選型 | 描述 | 默認值 |
---|---|---|
"name" | task 名字 | 無,必須指定 |
"type" | 須要建立的 task Class | DefaultTask |
"action" | 當 task 執行的時候,須要執行的閉包 closure 或 行爲 Action | null |
"overwrite" | 替換一個已存在的 task | false |
"dependsOn" | 該 task 所依賴的 task 集合 | [] |
"group" | 該 task 所屬組 | null |
"description" | task 的描述信息 | null |
"constructorArgs" | 傳遞到 task Class 構造器中的參數 | null |
在這裏,咱們能夠 在當前 task 中使用 "$" 來引用另外一個 task 的屬性,示例代碼以下所示:
task Gradle_First() {
}
task Gradle_Last() {
doLast {
println "I am not $Gradle_First.name"
}
}
複製代碼
固然,除了使用已有的屬性以外,咱們也能夠 使用 ext 給 task 自定義須要的屬性,代碼以下所示:
task Gradle_First() {
ext.good = true
}
task Gradle_Last() {
doFirst {
println Gradle_First.good
}
doLast {
println "I am not $Gradle_First.name"
}
}
複製代碼
此外,咱們也能夠 使用 defaultTasks 關鍵字 來將一些任務標識爲默認的執行任務,代碼以下所示:
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"
}
}
複製代碼
每一個 task 都會經歷 初始化、配置、執行 這一套完整的生命週期流程。
Task 一般使用 doFirst 與 doLast 兩個方式用於在執行期間進行操做。其示例代碼以下所示:
// 使用 Task 在執行階段進行操做
task myTask3(group: "MyTask", description: "task3") {
println "This is myTask3"
doFirst {
// 老二
println "This group is 2"
}
doLast {
// 老三
println "This description is 3"
}
}
// 也可使用 taskName.doxxx 的方式添加執行任務
myTask3.doFirst {
// 這種方式的最早執行 => 老大
println "This group is 1"
}
複製代碼
接下來,咱們就使用 doFirst 與 doLast 來進行一下實戰,來實現 計算 build 執行期間的耗時,其完整代碼以下所示:
// Task 執行實戰:計算 build 執行期間的耗時
def startBuildTime, endBuildTime
// 一、在 Gradle 配置階段完成以後進行操做,
// 以此保證要執行的 task 配置完畢
this.afterEvaluate { Project project ->
// 二、找到當前 project 下第一個執行的 task,即 preBuild task
def preBuildTask = project.tasks.getByName("preBuild")
preBuildTask.doFirst {
// 三、獲取第一個 task 開始執行時刻的時間戳
startBuildTime = System.currentTimeMillis()
}
// 四、找到當前 project 下最後一個執行的 task,即 build task
def buildTask = project.tasks.getByName("build")
buildTask.doLast {
// 五、獲取最後一個 task 執行完成前一瞬間的時間戳
endBuildTime = System.currentTimeMillis()
// 六、輸出 build 執行期間的耗時
println "Current project execute time is ${endBuildTime - startBuildTime}"
}
}
複製代碼
指定 Task 的執行順序有 三種 方式,以下圖所示:
dependsOn 強依賴的方式能夠細分爲 靜態依賴和動態依賴,示例代碼以下所示:
task task1 {
doLast {
println "This is task1"
}
}
task task2 {
doLast {
println "This is task2"
}
}
// Task 靜態依賴方式1 (經常使用)
task task3(dependsOn: [task1, task2]) {
doLast {
println "This is task3"
}
}
// Task 靜態依賴方式2
task3.dependsOn(task1, task2)
複製代碼
// Task 動態依賴方式
task dytask4 {
dependsOn this.tasks.findAll { task ->
return task.name.startsWith("task")
}
doLast {
println "This is task4"
}
}
複製代碼
咱們也能夠經過 Task 來指定輸入輸出,使用這種方式咱們能夠 高效地實現一個 自動維護版本發佈文檔的 gradle 腳本,其中輸入輸出相關的代碼以下所示:
task writeTask {
inputs.property('versionCode', this.versionCode)
inputs.property('versionName', this.versionName)
inputs.property('versionInfo', this.versionInfo)
// 一、指定輸出文件爲 destFile
outputs.file this.destFile
doLast {
//將輸入的內容寫入到輸出文件中去
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()
// 寫入版本信息到 XML 文件
...
}
task readTask {
// 二、指定輸入文件爲上一個 task(writeTask) 的輸出文件 destFile
inputs.file this.destFile
doLast {
//讀取輸入文件的內容並顯示
def file = inputs.files.singleFile
println file.text
}
}
task outputwithinputTask {
// 三、先執行寫入,再執行讀取
dependsOn writeTask, readTask
doLast {
println '輸入輸出任務結束'
}
}
複製代碼
首先,咱們定義了一個 WirteTask,而後,在註釋1處,指定了輸出文件爲 destFile, 並寫入版本信息到 XML 文件。接着,定義了一個 readTask,並在註釋2處,指定輸入文件爲上一個 task(即 writeTask) 的輸出文件。最後,在註釋3處,使用 dependsOn 將這兩個 task 關聯起來,此時輸入與輸出的順序是會先執行寫入,再執行讀取。這樣,一個輸入輸出的實際案例就實現了。若是想要查看完整的實現代碼,請查看 Awesome-WanAndroid 的 releaseinfo.gradle 腳本。
此外,在 McImage 中就利用了 dependsOn 的方式將自身的 task 插入到了 Gradle 的構建流程之中,關鍵代碼以下所示:
// 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))
複製代碼
除了 dependsOn 的方式,咱們還能夠在 task 閉包中經過 mustRunAfter 方法指定 task 的依賴順序,須要注意的是,在最新的 gradle api 中,mustRunAfter 必須結合 dependsOn 強依賴進行配套使用,其示例代碼以下所示:
// 經過 API 指定依賴順序
task taskX {
mustRunAfter "taskY"
doFirst {
println "this is taskX"
}
}
task taskY {
// 使用 mustRunAfter 指定依賴的(一至多個)前置 task
// 也可使用 shouldRunAfter 的方式,可是是非強制的依賴
// shouldRunAfter taskA
doFirst {
println "this is taskY"
}
}
task taskZ(dependsOn: [taskX, taskY]) {
mustRunAfter "taskY"
doFirst {
println "this is taskZ"
}
}
複製代碼
除了定義一個新的 task 以外,咱們也能夠使用 type 屬性來直接使用一個已有的 task 類型(不少文章都說的是繼承一個已有的類,不是很準確),好比 Gradle 自帶的 Copy、Delete、Sync task
等等。示例代碼以下所示:
// 一、刪除根目錄下的 build 文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 二、將 doc 複製到 build/target 目錄下
task copyDocs(type: Copy) {
from 'src/main/doc'
into 'build/target/doc'
}
// 三、執行時會複製源文件到目標目錄,而後從目標目錄刪除全部非複製文件
task syncFile(type:Sync) {
from 'src/main/doc'
into 'build/target/doc'
}
複製代碼
咱們可使用 gradle 提供的一系列生命週期 API 去掛接咱們本身的 task 到構建生命週期之中,好比使用 afterEvaluate 方法 將咱們第三小節定義的 writeTask 掛接到 gradle 配置完全部的 task 以後的時刻,示例代碼以下所示:
// 在配置階段執行完以後執行 writeTask
this.project.afterEvaluate { project ->
def buildTask = project.tasks.findByName("build")
doLast {
buildTask.doLast {
writeTask.execute()
}
}
}
複製代碼
須要注意的是,配置完成以後,咱們須要在 app moudle 下引入咱們定義的 releaseinfo 腳本,引入方式以下:
apply from: this.project.file("releaseinfo.gradle")
複製代碼
SourceSet 主要是 用來設置咱們項目中源碼或資源的位置的,目前它最多見的兩個使用案例就是以下 兩類:
咱們僅需在 app moudle 下的 android 閉包下配置以下代碼便可修改 so 庫存放位置:
android {
...
sourceSets {
main {
// 修改 so 庫存放位置
jniLibs.srcDirs = ["libs"]
}
}
}
複製代碼
一樣,在 app moudle 下的 android 閉包下配置以下代碼便可將資源文件進行分包存放:
android {
sourceSets {
main {
res.srcDirs = ["src/main/res",
"src/main/res-play",
"src/main/res-shop"
...
]
}
}
}
複製代碼
此外,咱們也可使用以下代碼 將 sourceSets 在 android 閉包的外部進行定義:
this.android.sourceSets {
...
}
複製代碼
Gradle 的命令有不少,可是咱們一般只會使用以下兩種類型的命令:
// 一、按自頂向下的結構列出子項目的名稱列表
./gradlew projects
// 二、分類列出項目中全部的任務
./gradlew tasks
// 三、列出項目的依賴列表
./gradlew dependencies
複製代碼
常規的用於執行 task 的命令有 四種,以下所示:
// 一、用於執行多個 task 任務
./gradlew JsonChao Gradle_Last
// 二、使用 -x 排除單個 task 任務
./gradlew -x JsonChao
// 三、使用 -continue 能夠在構建失敗後繼續執行下面的構建命令
./gradlew -continue JsonChao
// 四、建議使用簡化的 task name 去執行 task,下面的命令用於執行
// Gradle_Last 這個 task
./gradlew G_Last
複製代碼
而對於子目錄下定義的 task,咱們一般會使用以下的命令來執行它:
// 一、使用 -b 執行 app 目錄下定義的 task
./gradlew -b app/build.gradle MyTask
// 二、在大型項目中咱們通常使用更加智能的 -p 來替代 -b
./gradlew -p app MyTask
複製代碼
至此,咱們就將 Gradle 的核心 API 部分講解完畢了,這裏咱們再來回顧一下本文的要點,以下所示:
Gradle 的核心 API 很是重要,這對咱們高效實現一個 Gradle 插件無疑是必不可少的。由於 只有紮實基礎才能走的更遠,願咱們能一同前行。
歡迎關注個人微信:
bcce5360
因爲微信羣已超過 200 人,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~