轉(zhuǎn)載 Gradle基礎(chǔ)

原文地址

作為Android開發(fā)你必須明白的Gradle基礎(chǔ)

作為一個Android開發(fā)程序員,如果你的build.gradle都只能靠IDE生成或者從別的項目中復(fù)制粘貼來完成,那么你該好好的看完這篇文章,掌握一下你不知道的Gradle基礎(chǔ)。

文中的圖片均來自于網(wǎng)絡(luò),侵刪

Gradle是一個基于JVM的構(gòu)建工具,目前Android Studio中建立的工程都是基于gradle進行構(gòu)建的。Gradle的與其他構(gòu)建工具(ant、maven)的特性主要包括:

  • 強大的DSL和豐富的gradle的API
  • gradle就是groovy
  • 強大的依賴管理
  • 可拓展性
  • 與其他構(gòu)建工具的集成

三種構(gòu)建腳本

Gradle的腳本都是配置型腳本。每一種腳本類型實際上都是某個具體的gradle的API中的類對象的委托,腳本執(zhí)行對應(yīng)的其實是其委托的對象的配置。在一個完整的gradle的構(gòu)建體系中,總共有三種類型的構(gòu)建腳本,同時也分別對應(yīng)著三種委托對象

腳本類型 委托對象
Init script Gradle
Settings script Settings
Build script Project

init.gradle

對應(yīng)的就是上面的Init script,實際上就是Gradle對象的委托,所以在這個init 腳本中調(diào)用的任何屬性引用以及方法,都會委托給這個 Gradle 實例。

Init script的執(zhí)行發(fā)生在構(gòu)建開始之前,也是整個構(gòu)建最早的一步。

配置Init scrip的依賴

每個腳本的執(zhí)行都可以配置當(dāng)前腳本本身執(zhí)行所需要的依賴項。Init scrip的配置如下:

// initscript配置塊包含的內(nèi)容就是指當(dāng)前腳本本身的執(zhí)行所需要的配置
// 我們可以在其中配置比如依賴路徑等等
initscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'org.apache.commons', name: 'commons-math', version: '2.0'
    }
}

使用Init scrip

要使用一個定義好的Init scrip,主要有以下幾個方式

  • 在執(zhí)行g(shù)radle命令的時候,通過-I--init-script命令選項指定腳本的路徑

    這種方式可以針對具體的一次構(gòu)建。

  • 把一個init.gradle文件放到 *USER_HOME*/.gradle/ 目錄

  • 把一個文件名以.gradle結(jié)尾的文件放到Gradle 分發(fā)包*GRADLE_HOME*/init.d/ 目錄內(nèi)

    以上的兩種方式是全局的,對機器內(nèi)的構(gòu)建都會起作用

settings.gradle

對應(yīng)的是Settings script腳本類型,是Settings對象的委托。在 腳本中調(diào)用的任何屬性引用以及方法,都會委托給這個 Settings 實例。

Settings script的執(zhí)行發(fā)生在gradle的構(gòu)建生命周期中的初始化階段。Settings腳本文件中聲明了構(gòu)建所需要的配置,并用以實例化項目的層次結(jié)構(gòu)。在執(zhí)行settings腳本并初始化Settings對象實例的時候,會自動的構(gòu)建一個根項目對象rootProject并參與到整個構(gòu)建當(dāng)中。(rootProject默認的名稱就是其文件夾的名稱,其路徑就是包含setting腳本文件的路徑)。

下面是一張關(guān)于Settings對象的類圖:

image.png

每一個通過include方法被添加進構(gòu)建過程的project對象,都會在settings腳本中創(chuàng)造一個ProjectDescriptor的對象實例。

因此,在settings的腳本文件中,我們可以訪問使用的對象包括:

  • Settings對象
  • Gradle對象
  • ProjectDescriptor對象

獲取settings文件

在gradle中,只要根項目/任何子項目的目錄中包含有構(gòu)件文件,那么就可以在相應(yīng)的位置運行構(gòu)建。而判斷一個構(gòu)建是否是多項目的構(gòu)建,則是通過尋找settings腳本文件,因為它指示了子項目是否包含在多項目的構(gòu)建中。

查找settings文件的步驟如下:

  1. 在與當(dāng)前目錄同層次的master目錄中搜索setting文件
  2. 如果在1中沒有找到settings文件,則從當(dāng)前目錄開始在父目錄中查找settings文件。

當(dāng)找到settings文件并且文件定義中包含了當(dāng)前目錄,則當(dāng)前目錄就會被認為是多項目的構(gòu)建中的一部分。

build.gradle

對應(yīng)的就是前面提到的Build script腳本類型,是gradle中Project對象的委托。在腳本中調(diào)用的任何屬性引用以及方法,都會委托給這個 Project 實例。

配置腳本依賴

在build.gradle文件中有一個配置塊buildScipt{}是用于配置當(dāng)前腳本執(zhí)行所需的路徑配置等的(與initScript形似)。

buildscript {
    // 這里的repositories配置塊要與Project實例當(dāng)中的repositories區(qū)分開來
    // 這里的repositories配置是指腳本本身依賴的倉庫源,其委托的對象實際上是ScriptHandler
    repositories {
        mavenLocal()
        google()
        jcenter()
    }
    // 與前面的repositories配置塊相同,也要與Project當(dāng)中的dependencies配置塊區(qū)分開來
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
    }
}

這里補充關(guān)鍵的一點,在build.gradle文件中,不管buildScript{}配置塊被放在哪個位置,它總是整個腳本文件中最先被執(zhí)行的

三個構(gòu)建塊

每個gradle構(gòu)建都包含三個基本的構(gòu)件塊:

  • project
  • task
  • property

每個構(gòu)建包含至少一個project,進而又包含一個或者多個task。project和task暴露的屬性(property)可以用來控制構(gòu)建。

Project

我們對project的理解更多來源于項目目錄中的build.gradle文件(因為它其實就是project對象的委托)。Project對象的類圖如下所示:

image.png

項目配置

在build.gradle腳本文件中,我們不僅可以對單獨project進行配置,也可以定義project塊的共有邏輯等,參考下面的定義。


image.png

常見的例子比如:

// 為所有項目添加倉庫源配置
allprojects {
    repositories {
        jcenter()
        google()
    }
}
// 為所有子項目添加mavenPublish的配置塊
subprojects {
    mavenPublish {
        groupId = maven.config.groupId
        releaseRepo = maven.config.releaseRepo
        snapshotRepo = maven.config.snapshotRepo
    }
}

Task

任務(wù)是gradle構(gòu)建的基礎(chǔ)配置塊之一,gradle的構(gòu)建的執(zhí)行就是task的執(zhí)行。下面是task的類圖。

image.png

task的配置和動作

當(dāng)我們定一個一個task的時候,會包含配置和動作兩部分的內(nèi)容。比如下面的代碼示例:

task test{
    println("這是配置")

    doFirst{
        // do something here
    }
    doLast(){
        // do something here
    }
}

目前task的動作(action)聲明主要包含兩個方法:

  • doFirst
  • doLast

這些動作是在gradle的構(gòu)建生命周期中的執(zhí)行階段被調(diào)用。值得注意的是,一個task可以聲明多個doFirstdoLast動作。也可以為一些已有的插件中定義的task添加動作。比如:

// 為test任務(wù)添加一個doLast的動作
test.doLast{
    // do something here
}

在task的定義之中,除了動作塊以外的是配置塊,我們可以聲明變量、訪問屬性、調(diào)用方法等等。這些配置塊的內(nèi)容發(fā)生在gradle的構(gòu)建生命周期中的配置階段。因此task中的配置每次都會被執(zhí)行。(動作塊只有在實際發(fā)生task的調(diào)用的時候才會執(zhí)行)。

task的依賴

gradle中任務(wù)的執(zhí)行順序是不確定的。通過task之間的依賴關(guān)系,gradle能夠確保所依賴的task會被當(dāng)前的task先執(zhí)行。使用task的dependsOn()方法,允許我們?yōu)閠ask聲明一個或者多個task依賴。

task first{
    doLast{
        println("first")
    }
}

task second{
    doLast{
        println("second")
    }
}

task third{
    doLast{
        println("third")
    }
}

task test(dependsOn:[second,first]){
    doLast{
        println("first")
    }
}

third.dependsOn(test)

task的類型

默認情況下,我們常見的task都是org.gradle.api.DefaultTask類型。但是在gradle當(dāng)中有相當(dāng)豐富的task類型我們可以直接使用。要更改task的類型,我們可以參考下面的示例

task createDistribution(type:Zip){

}

更多關(guān)于task的類型,可以參考gradle的官方文檔

Property

屬性是貫穿在gradle構(gòu)建始終的,用于幫助控制構(gòu)建邏輯的存在。gradle中聲明屬性主要有以下兩種方式:

  • 使用ext命名空間定義拓展屬性
  • 使用gradle屬性文件gradle.properties定義屬性

ext命名空間

Gradle中很多模型類都提供了特別的屬性支持,比如Project.在gradle內(nèi)部,這些屬性會以鍵值對的形式存儲。使用ext命名空間,我們可以方便的添加屬性。下面的方式都是支持的:

//在project中添加一個名為groupId的屬性
project.ext.groupId="tech.easily"
// 使用ext塊添加屬性
ext{
    artifactId='EasyDependency'
    config=[
            key:'value'
    ]
}

值得注意的是,只有在聲明屬性的時候我們需要使用ext命名空間,在使用屬性的時候,ext命名空間是可以省略的。

屬性文件

正如我們經(jīng)常在Android項目中看到的,我們可以在項目的根目錄下新建一個gradle.properties文件,并在文件中定義簡單的鍵值對形式的屬性。這些屬性能夠被項目中的gradle腳本所訪問。如下所示:

# gradle.properties
# 注意文件的注釋是以#開頭的
groupId=tech.easily
artifactId=EasyDependency

有的時候,我們可能需要在代碼中動態(tài)的創(chuàng)建屬性文件并讀取文件中的屬性(比如自定義插件的時候),我們可以使用java.util.Properties類。比如:

void createPropertyFile() {
    def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
    def defaultProps = new Properties()
    if (!localPropFile.exists()) {
        localPropFile.createNewFile()
        defaultProps.setProperty("debuggable", 'true')
        defaultProps.setProperty("groupId", GROUP)
        defaultProps.setProperty("artifactId", project.name)
        defaultProps.setProperty("versionName", VERSION_NAME)
        defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")
    } else {
        localPropFile.withInputStream { stream ->
            defaultProps.load(stream)
        }
    }
}

關(guān)于屬性很重要的一點是屬性是可以繼承的。在一個項目中定義的屬性會自動的被其子項目繼承,不管我們是用以上哪種方式添加屬性都是適用的。

構(gòu)建生命周期

前面提及到了gradle中多種腳本類型,并且他們都在不同的生命周期中被執(zhí)行。

三個階段

在gradle構(gòu)建中,構(gòu)建的生命周期主要包括以下三個階段:

  • 初始化(Initialization)

    如前文所述,在這個階段,settings腳本會被執(zhí)行,從而Gradle會確認哪些項目會參與構(gòu)建。然后為每一個項目創(chuàng)建 Project 對象。

  • 配置(Configuration)

    配置 Initialization 階段創(chuàng)建的Project 對象,所有的配置腳本都會被執(zhí)行。(包括Project中定義的task的配置塊也都會被執(zhí)行)

  • 執(zhí)行(Configuration)

    這個階段Gradle會確認哪些在 Configuration 階段創(chuàng)建和配置的 Task 會被執(zhí)行,哪些 Task會被執(zhí)行取決于gradle命令的參數(shù)以及當(dāng)前的目錄,確認之后便會執(zhí)行

監(jiān)聽生命周期

在gradle的構(gòu)建過程中,gradle為我們提供了非常豐富的鉤子,幫助我們針對項目的需求定制構(gòu)建的邏輯,如下圖所示:

image.png

要監(jiān)聽這些生命周期,主要有兩種方式:

  • 添加監(jiān)聽器
  • 使用鉤子的配置塊

關(guān)于可用的鉤子可以參考GradleProject中的定義,常用的鉤子包括:

Gradle

  • beforeProject()/afterProject()

    等同于Project中的beforeEvaluateafterEvaluate

  • settingsEvaluated()

    settings腳本被執(zhí)行完畢,Settings對象配置完畢

  • projectsLoaded()

    所有參與構(gòu)建的項目都從settings中創(chuàng)建完畢

  • projectsEvaluated()

    所有參與構(gòu)建的項目都已經(jīng)被評估完

TaskExecutionGraph

  • whenReady()

    task圖生成。所有需要被執(zhí)行的task已經(jīng)task之間的依賴關(guān)系都已經(jīng)確立

Project

  • beforeEvaluate()
  • afterEvaluate()

依賴管理

在前面提及的Gradle的主要特性之中,其中的一點就是強大的依賴管理。Gradle中具備豐富的依賴類型,兼容多種依賴倉庫。同時Gradle中的每一項依賴都是基于特定的范圍(scope)進行分組管理的。

在gradle中添加為項目添加依賴的方式如下所示:

// build.gradle

// 添加依賴倉庫源
repositories {
    google()
    mavenCentral()
}
// 添加依賴
// 依賴類型包括:文件依賴、項目依賴、模塊依賴
dependencies {
    // local dependencies.
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ...
}

四種依賴類型

Gradle中的依賴類型有四類:

  • 模塊依賴

    這是gradle中比較常見的依賴類型, 它通常指向倉庫中的一個構(gòu)件,如下所示:

    dependencies {
        runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
        runtime 'org.springframework:spring-core:2.5',
                'org.springframework:spring-aop:2.5'
        runtime(
            [group: 'org.springframework', name: 'spring-core', version: '2.5'],
            [group: 'org.springframework', name: 'spring-aop', version: '2.5']
        )
        runtime('org.hibernate:hibernate:3.0.5') {
            transitive = true
        }
        runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
        runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
            transitive = true
        }
    }
    
    

    模塊依賴對應(yīng)于gradle的API中的 ExternalModuleDependency對象

  • 文件依賴

    dependencies {
        runtime files('libs/a.jar', 'libs/b.jar')
        runtime fileTree(dir: 'libs', include: '*.jar')
    }
    
    
  • 項目依賴

    dependencies {
        compile project(':shared')
    }
    
    

    項目依賴對應(yīng)于gradle的API中的 ProjectDependency對象

  • 特定的Gradle發(fā)行版依賴

    dependencies {
        compile gradleApi()
        testCompile gradleTestKit()
        compile localGroovy()
    }
    
    

管理依賴配置

gradle中項目的每一項依賴都是應(yīng)用于一個特定的范圍的,在gradle中用 Configuration對象表示。每一個Configuration對象都會有一個唯一的名稱。Gradle的依賴配置管理如下所示:

image.png

自定義Configuration

在gradle中,自定義Configuration對象是非常簡單的,同時定義自己的Configuration對象的時候,也可以繼承于已有的Configuration對象,如下所示:

configurations {
    jasper
    // 定義繼承關(guān)系
    smokeTest.extendsFrom testImplementation
}

repositories {
    mavenCentral()
}

dependencies {
    jasper 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2'
}

管理傳遞性依賴

在實際的項目依賴管理中存在這樣的一種依賴關(guān)系:

  • 模塊b依賴于模塊c
  • 模塊a依賴于模塊b
  • 模塊c成為了模塊a的傳遞依賴

在處理上面這種傳遞性依賴的時候,gradle提供了強大的管理功能

使用依賴約束

依賴約束可以幫助我們控制傳遞性依賴以及自身的依賴的版本號(版本范圍),比如:

dependencies {
    implementation 'org.apache.httpcomponents:httpclient'
    constraints {
        // 這里httpclient是項目本身的依賴
        // 這個約束表示,不管是項目本身的依賴是還是傳遞依賴都強制使用這個指定的版本號
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because 'previous versions have a bug impacting this application'
        }
        // commons-codec并沒有被聲明為項目本身的依賴
        // 所以僅當(dāng)commons-codec是傳遞性依賴的時候這段邏輯才會被觸發(fā)
        implementation('commons-codec:commons-codec:1.11') {
            because 'version 1.9 pulled from httpclient has bugs affecting this application'
        }
    }
}

排除特定的傳遞性依賴

有的時候,我們所依賴的項目/模塊會引入多個傳遞性依賴。而其中部分的傳遞性依賴我們是不需要的,這時候可以使用exclude排除部分的傳遞性依賴,如下所示:

dependencies {
    implementation('log4j:log4j:1.2.15') {
        exclude group: 'javax.jms', module: 'jms'
        exclude group: 'com.sun.jdmk', module: 'jmxtools'
        exclude group: 'com.sun.jmx', module: 'jmxri'
    }
}

強制使用指定的依賴版本

Gradle通過選擇依賴關(guān)系圖中找到的最新版本來解決任何依賴版本沖突。 可是有的時候,某些項目會需要使用一個較老的版本號作為依賴。這時候我們可以強制指定某一個版本。例如:

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
    // 假設(shè)commons-codec的最新版本是1.10
    implementation('commons-codec:commons-codec:1.9') {
        force = true
    }
}

要注意的是,如果依賴項目中使用了新版本才有的api,而我們強制使用了舊版本的傳遞依賴之后,會引起運行時的錯誤

禁止傳遞性依賴

dependencies {
    implementation('com.google.guava:guava:23.0') {
        transitive = false
    }
}

在android plugin升級到3.0以后,提供了一種新的依賴配置項implement,它的作用就是解決了依賴傳遞性的問題。模塊本身的依賴并不會暴露給被引用的項目

依賴關(guān)系解析

使用依賴關(guān)系解析規(guī)則

依賴關(guān)系解析規(guī)則提供了一種非常強大的方法來控制依賴關(guān)系解析過程,并可用于實現(xiàn)依賴管理中的各種高級模式。比如:

  • 統(tǒng)一構(gòu)件組的版本

    很多時候我們依賴一個公司的庫會包含多個module,這些module一般都是統(tǒng)一構(gòu)建、打包和發(fā)布的,具備相同的版本號。這個時候我們可以通過控制依賴關(guān)系的解析過程做到版本號統(tǒng)一。

    configurations.all {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            if (details.requested.group == 'org.gradle') {
                details.useVersion '1.4'
                details.because 'API breakage in higher versions'
            }
        }
    }
    
    
  • 處理自定義的版本scheme

    configurations.all {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            if (details.requested.version == 'default') {
                def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
                details.useVersion version.version
                details.because version.because
            }
        }
    }
    
    def findDefaultVersionInCatalog(String group, String name) {
        //some custom logic that resolves the default version into a specific version
        [version: "1.0", because: 'tested by QA']
    }
    
    

關(guān)于更多依賴關(guān)系解析規(guī)則的使用實例可以參考gradle的API中的 ResolutionStrategy

使用依賴關(guān)系的替代規(guī)則

依賴關(guān)系的替換規(guī)則和上面的依賴關(guān)系解析規(guī)則有點相似。實際上,依賴關(guān)系解析規(guī)則的許多功能可以通過依賴關(guān)系替換規(guī)則來實現(xiàn)。依賴關(guān)系的替換規(guī)則允許項目依賴(Project Dependency)和模塊依賴(Module Dependency)被指定的替換規(guī)則透明地替換。

// 使用項目依賴替換模塊依賴
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("org.utils:api") with project(":api") because "we work with the unreleased development version"
        substitute module("org.utils:util:2.5") with project(":util")
    }
}
// 使用模塊依賴替換項目依賴
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute project(":api") with module("org.utils:api:1.3") because "we use a stable version of utils"
    }
}

除了上面兩種之外,還有其他三種的依賴關(guān)系規(guī)則處理。因為沒有實際使用過,這里不過多闡述,想了解更多可以查看官方的文檔Customizing Dependency Resolution Behavior

  • 使用組件元數(shù)據(jù)(meta-data)規(guī)則
  • 使用組件選擇規(guī)則
  • 使用模塊更換規(guī)則

插件開發(fā)

插件開發(fā)是gradle靈活的構(gòu)建體系中的一個強大工具。通過gradle中的PluginAPI,我們可以自定義插件,把一些通用的構(gòu)建邏輯插件化并廣泛的運用。比如Android項目中都會使用的:com.android.application,kotlin-android,java等等。

網(wǎng)上關(guān)于插件開發(fā)的文章已經(jīng)很多,這里不再贅述。這里推薦我寫的一個Gradle插件,也是在我完全看了gradle的官方文檔之后,結(jié)合前面提及到的依賴管理的知識寫的:

  • EasyDependency

    一個幫助提高組件化開發(fā)效率的gradle插件,提供的功能包括:

    1. 發(fā)布模塊的構(gòu)件都遠程maven倉庫
    2. 動態(tài)更換依賴配置:對模塊使用源碼依賴或者maven倉庫的構(gòu)件(aar/jar)依賴

寫在最后

全文基本是在看了gradle的官方文檔及相關(guān)資料之后,按照自己的思路的整理和總結(jié)。關(guān)于gradle的使用和問題歡迎一起討論。

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

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

  • 說明 本文主要從實現(xiàn)原理和代碼層面介紹Gradle開發(fā)相關(guān)知識。關(guān)于本文中提到的、Gradle中的基本概念等內(nèi)容,...
    搬磚的小明閱讀 8,135評論 1 33
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,525評論 19 139
  • 說明 本文主要介紹和Gradle關(guān)系密切、相對不容易理解的配置,偏重概念介紹。部分內(nèi)容是Android特有的(例如...
    搬磚的小明閱讀 16,145評論 1 62
  • 參考資料:http://gold.xitu.io/post/580c85768ac247005b5472f9htt...
    zhaoyubetter閱讀 11,239評論 0 6
  • 齊元愿望:學(xué)習(xí)金剛種子法則,財富自由,家庭關(guān)系和諧美滿,家人身體健康,姐姐生活幸福美滿。 ...
    齊元滿愿閱讀 200評論 0 0

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