讀懂 Gradle 的 DSL


現在 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);
    // ...
}

我們看到 dependenciesProject 一個方法,為什么可以在 build.gradle 調用 Project 的方法呢,官方文檔里面有相關的介紹。一個 Gradle 項目一般有一個 settings.gradle 文件和一個 build.gradle 文件,settings.gradle 用來配置目錄結構,子工程就是在 settings.gradle 里面配置,Projectbuild.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_demojava_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);

repositoriesbuild.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 中指定。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM