現在 Android 開發免不了要和 Gradle 打交道,所有的 Android 開發肯定都知道這麼在 中添加依賴,或者添加配置批量打包,但是真正理解這些腳本的人恐怕很少。其實 Gradle 的 可以說是一個代碼文件,熟悉 Java 的人理解起來很簡單的,之所以不願意去涉及,主要感覺沒有必要去研究 ...
現在 Android 開發免不了要和 Gradle 打交道,所有的 Android 開發肯定都知道這麼在 build.gradle
中添加依賴,或者添加配置批量打包,但是真正理解這些腳本的人恐怕很少。其實 Gradle 的 build.gradle
可以說是一個代碼文件,熟悉 Java 的人理解起來很簡單的,之所以不願意去涉及,主要感覺沒有必要去研究。要能看懂 build.gradle
,除了要瞭解 Groovy 的語法,還要瞭解 Gradle 的構建流程,要研究還是要花一些時間的,所以這篇文章可以讓一個 Java 程式員在一個小時內看懂 Gradle 的腳本。
Gradle 簡單介紹
Gradle 構建由 Project 和 Task 組成,Project 保存項目的屬性,例如 name,版本號,代碼文件位置。Task 也是 Project 的一部分,但是它是可執行的任務,我們最常使用的 build
就是一個 Task,Task 可以依賴於另外一個 Task,一個 Task 在執行的時候,它依賴的 Task 會先執行。這樣,當我們 build 的時候,這個 Task 可能依賴很多的 Task,比如代碼檢查、註解處理,這樣一層層的依賴,最終通過 build Task 全部執行。
Gradle 和 Groovy
Gradle 和 Groovy 這兩個名字很容易讓人產生混淆,這裡先解釋一下,Groovy 是一門編程語言,和 Java 一樣。Gradle 和一個自動化構建工具,其他知名的構建工具還有 Maven 和 Ant。什麼自動化構建工具?用 Android 來舉例,打包一個 Apk 文件要做很多工作,代碼預處理,lint代碼檢查、處理資源、編譯 Java 文件等等,使用自動化構建工具,一個命令就可以生成 Apk 了。
Gradle 的 DSL 目前支持兩種語言的格式,Groovy 和 Kotlin,Kotlin 格式的 DSL 是在 5.0 引入的,相比 Groovy,Kotlin 使用的人數更多,也更好理解,在這兒主要介紹 Groovy 格式的 DSL。
介紹一下什麼是 DSL,DSL 是 Domain Specific Language
的縮寫,既領域專用語言。Gradle 的 DSL 專門用於配置項目的構建,不能做其他工作,而像 Java 、C/C++ 這些就屬於通用語言,可以做任何工作。
我們還要理解什麼是腳本文件。在寫代碼 Java 代碼時,程式是從 main()
函數開始執行的,只有在 main()
中調用的代碼才會執行。但是腳本文件不一樣,只要在文件內寫的代碼都會執行,Groovy
是支持腳本文件的,我們配置好 Groovy 的開發環境,新建一個文件 test.groovy
,輸入以下內容:
String hello = "Hello World!"
println(hello)
println("The End")
然後運行:
groovy test.groovy
輸出結果為:
Hello World!
The End
雖然沒有 main
函數,但是裡面的代碼都執行了。很明顯,build.gradle
就是一個 Groovy 的腳本文件,裡面就是 Groovy 代碼,裡面添加的所有代碼都會運行,我們可以試驗以下,隨便打開一個 Gradle 格式的項目,在 build.gradle
最下麵添加一些 Java 代碼:
String hello = "Hello World!"
System.out.println(hello)
然後執行:
./gradlew -q # -q 是不輸出額外的信息
我們會看到輸出了 Hellow World
,說明我們添加的代碼被執行了,那麼為什麼可以在 build.gradle
裡面寫 Java 代碼呢?這是因為 Groovy 是支持 Java 的語法的,在 Groovy 文件寫 Java 代碼是完全沒有問題的。
build.gradle
的執行方式
現在總結一下,build.gradle
就是一個 Groovy 格式腳本文件,裡面是 Groovy 或者 Java 代碼,構建的時候會順序執行,但是打開 build.gradle
,可能還是一頭霧水,一個個字元和大括弧組成的東西到底是什麼鬼?我們來看一下最長使用的 dependencies
:
dependencies {
// This dependency is found on compile classpath of this component and consumers.
implementation 'com.google.guava:guava:26.0-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}
implementation
也可以這樣寫:
implementation('com.google.guava:guava:26.0-jre')
implementation
其實就是一個函數,在 Groovy 中,函數調用可以使用空格加參數的形式調用,例如
void foo(String params1, int param2) {
println("param1 = $params1, param2 = $param2")
}
foo "aaa", 2
implementation 'com.google.guava:guava:26.0-jre'
就是調用了 implementation
函數添加了一個依賴。以此類推,dependencies
也是一個函數,在 IDEA
中,我們可以直接 Ctrl
加滑鼠左鍵點擊進去看它的聲明:
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
// ...
void dependencies(Closure configureClosure);
// ...
}
我們看到 dependencies
是 Project
一個方法,為什麼可以在 build.gradle
調用 Project
的方法呢,官方文檔裡面有相關的介紹。一個 Gradle 項目一般有一個 settings.gradle
文件和一個 build.gradle
文件,settings.gradle
用來配置目錄結構,子工程就是在 settings.gradle
裡面配置,Project
和 build.gradle
是一一對應的關係,Gradle 的構建流程如下:
1、生成一個 Settings
對象,執行 settings.gradle
對這個對象進行配置
2、使用 Settings
對象生成工程結構,創建 Project
對象
3、對所有 Project
執行對應的 build.gradle
進行配置
build.gradle
就是對 Project
的操作,例如,在 build.gradle
中輸入以下代碼
println "project name is ${this.name}"
輸出結果為: project name is java_demo
,java_demo
就是我們的 project name,我們可以認為對 this
的操作就是對 project
的操作。
Groovy 也是有語法糖的,類的屬性可以直接使用名字,例如 Project
的有兩個函數:
Object getVersion();
void setVersion(Object version);
那麼這就說明 Project
有一個 version
屬性,在 build.gradle
中我們可以這樣來使用:
version = "1.0" // 賦值,調用 setVersion()
println version // 讀取,調用 getVersion()
在 Project
中沒有 getter
方法的屬性是不能賦值的,例如 name
,我們可以輸出 name
的值,但是 name = "demo"
是錯誤的。
所以,在 build.gradle
中的代碼就是修改 Project
,方式就是修改屬性或者調用相關的方法,plugins
方法是添加插件,repositories
方法是添加代碼倉庫,
Groovy 閉包
閉包可以認為是可以執行的代碼塊,Groovy 中閉包的聲明和執行方式如下:
Closure closure = { String item ->
println(item)
}
closure("Hello") // 執行
和 Lambda 表達式很像,但是 Groovy 的閉包可以先聲明,然後設置代理來執行,例如我們聲明一個閉包:
Closure closure = {
sayHello()
}
這個閉包裡面執行了 sayHello()
函數,但是我們沒有在任何地方聲明這個函數,在 Java 中,這是個編譯錯誤,但是 Groovy 是允許的,完整的執行的例子如下:
Closure closure = {
sayHello()
}
class Foo {
void sayHello() {
println("Hello!!!")
}
}
def foo = new Foo()
closure.delegate = foo
closure()
輸出結果為:
Hello!!!
我們為閉包設置了一個代理 delegate
,只要這個代理有 sayHello()
方法,代碼就能執行,這就是為什麼我們查看 Project
的源碼,裡面很多函數參數類型都是 Closure
,例如:
void repositories(Closure configureClosure);
void dependencies(Closure configureClosure);
repositories
在 build.gradle
中是這樣調用的:
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
我們通過 IDE 進入 jcenter()
的聲明,進入的是:
public interface RepositoryHandler extends ArtifactRepositoryContainer {
// ...
}
由於沒看過源碼,我也只能猜,我猜 repositories
這個閉包的 delegate
是一個 RepositoryHandler
,通過執行 RepositoryHandler
的方法,為工程添加 Repository
Plugin
來看我們使用最多的 dependencies
dependencies {
// This dependency is found on compile classpath of this component and consumers.
implementation 'com.google.guava:guava:26.0-jre'
implementation('com.google.guava:guava:26.0-jre')
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}
在 Java 和 Android 項目中 implementation
是一定會用到的,但是一個 Gradle Basic 項目是沒有 implementation
的,實際上,在 dependencies
是不能直接添加任何依賴的。
這裡我們有說一下 Gradle 怎麼解決依賴。
Gradle 空白項目沒有編譯 Java 項目的能力,但是它能從倉庫下載依賴的庫並且配置到 Project
中。在我們編譯 Java 項目的時候,一個配置是不夠的,至少要有個測試版,正式版,兩個版本依賴的庫可能是不一樣的,兩個版本部分代碼也是不一樣的,那麼我們怎麼區分呢?在 Gradle 中,是通過 configurations
,也就是配置,每個配置可以單獨的添加依賴,在編譯的時候,也就是執行某個 Task 的時候,通過讀取配置中的依賴來添加 classpath
,例如:
repositories {
mavenCentral()
}
configurations {
test
release
}
dependencies {
test 'org.apache.commons:commons-lang3:3.0'
release 'org.slf4j:slf4j-log4j12:1.7.2'
}
task buildTest {
doLast {
println configurations.test.name
println configurations.test.asPath
}
}
執行 ./gradlew buildTest -q
,輸出結果為:
test
/Users/xxx/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.0/8873bd0bb5cb9ee37f1b04578eb7e26fcdd44cb0/commons-lang3-3.0.jar
如果在 buildTest
這個 Task 中進行編譯工作的話,我們就可以直接讀取 configurations.test
的路徑設置為 classpath
。
implementation
就是通過添加了一個 implementation
配置來實現的。這個配置是通過:
plugins {
// Apply the java plugin to add support for Java
id 'java'
// Apply the application plugin to add support for building an application
id 'application'
}
添加的,我們通過 plugins
可以給 Project
添加屬性,Tasks,配置,例如我們寫一個最簡單的插件:
package com.demo
import org.gradle.api.Plugin
import org.gradle.api.Project
class DemoPlugin implements Plugin<Project> {
void apply(Project project) {
project.task("hello") {
doLast {
println "Hello World"
}
}
project.configurations {
demoCompile
}
}
}
這個插件為 Project
添加了一個 Task,添加了一個配置,我們將這個文件 DemoPlugin.groovy
放在項目根目錄下的 buildSrc/src/main/groovy/demo/
下,就可以在 build.gradle
中直接使用了:
apply plugin: com.demo.DemoPlugin
buildscript
對於 buildscript
,例如:
buildscript {
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
}
}
它的作用是為構建腳本提供依賴,例如我們在項目中使用了 Android 的 Plugin,這個 Plugin 的要從哪找下載?這就需要在 buildscript 中指定。