【Gradle深入淺出】——Gradle基礎(chǔ)概念

系列目錄

1.【Gradle深入淺出】——初識(shí)Gradle
2.【Gradle深入淺出】——Gradle基礎(chǔ)概念
3.【Gradle深入淺出】——Android Gradle Plugin 基礎(chǔ)概念
4.【Gradle深入淺出】——Gradle配置(一)
5.【Gradle深入淺出】——Gralde配置(二)

前言

前一篇博客從基礎(chǔ)層面對(duì)Gradle做了一個(gè)講解,讓我們對(duì)于Gradle有了一個(gè)大體上對(duì)認(rèn)知。本篇博客開(kāi)始就開(kāi)始進(jìn)入Gradle的學(xué)習(xí)了,本篇博客將從Gradle的基礎(chǔ)概念進(jìn)行講解,Gradle的基礎(chǔ)概念又分為Gradle和AGP(Android Gradle Plugin后續(xù)簡(jiǎn)稱(chēng)AGP)的基礎(chǔ)概念,所以這里會(huì)做一定的劃分,可能有的概念在兩邊都有涉及,所以區(qū)分不會(huì)特別明顯,但是對(duì)于我們學(xué)習(xí)來(lái)說(shuō),并不會(huì)造成什么困擾。

Gradle基礎(chǔ)概念

首先還是回到上篇博客一直圍繞的一個(gè)話(huà)題,Gradle是什么?
Gradle中的所有內(nèi)容都基于兩個(gè)基本概念:project和task
Gradle 是通過(guò)組織一系列 task 來(lái)最終完成自動(dòng)化構(gòu)建的,所以 task 是 Gradle 里最重要的概念
我們以生成一個(gè)可用的 apk 為例,整個(gè)過(guò)程要經(jīng)過(guò) 資源的處理,javac 編譯,dex 打包,apk 打包,簽名等等步驟,每個(gè)步驟就對(duì)應(yīng)到 gradle 里的一個(gè) task
gradle 可以類(lèi)比做一條流水線(xiàn),task 可以比作流水線(xiàn)上的機(jī)器人,每個(gè)機(jī)器人負(fù)責(zé)不同的事情,最終生成完整的構(gòu)建產(chǎn)物。


流水線(xiàn)

而Gradle的代碼實(shí)質(zhì)是配置腳本,執(zhí)行一種類(lèi)型的配置腳本時(shí)就會(huì)創(chuàng)建一個(gè)關(guān)聯(lián)的對(duì)象。
Gradle的三種主要對(duì)象解釋如下:

  • Project對(duì)象:每個(gè)build.gradle會(huì)轉(zhuǎn)換成一個(gè)Project對(duì)象。
  • Gradle對(duì)象:構(gòu)建初始化時(shí)創(chuàng)建,整個(gè)構(gòu)建執(zhí)行過(guò)程中只有這么一個(gè)對(duì)象,一般很少去修改這個(gè)默認(rèn)配置腳本。
  • Settings對(duì)象:每個(gè)settings.gradle會(huì)轉(zhuǎn)換成一個(gè)Settings對(duì)象。

Build的生命周期

Gradle構(gòu)建的生命周期其實(shí)相對(duì)來(lái)說(shuō)還比較復(fù)雜,這里先僅從大方面來(lái)講一下Gradle的生命周期,對(duì)于后續(xù)我們的理解有幫助,而對(duì)于具體Gradle提供的生命周期hook在后面再專(zhuān)門(mén)講解。
Gradle的構(gòu)建大體分為三個(gè)階段:

  • 初始化階段
    初始化階段主要做的事情是有哪些項(xiàng)目需要被構(gòu)建,也就是執(zhí)行我們的setting.gradle,構(gòu)建出Setting對(duì)象,并且創(chuàng)建對(duì)應(yīng)的Product對(duì)象。
  • 配置階段
    配置階段主要是根據(jù)上一步setting.gradle配置的項(xiàng)目,根據(jù)項(xiàng)目的build.grale進(jìn)行構(gòu)建,這時(shí)候就會(huì)根據(jù)build.gradle,并且生成相應(yīng)要執(zhí)行的Task
  • 執(zhí)行階段
    執(zhí)行階段就是根據(jù)上面的task,按照順序進(jìn)行執(zhí)行構(gòu)建。

Setting對(duì)象

上篇博客其實(shí)有提到,我們?cè)诙囗?xiàng)目構(gòu)建多時(shí)候,Setting.gradle就發(fā)揮了很大多作用,這個(gè)文件一般放在工程多根目錄,該文件在初始化階段被執(zhí)行,通過(guò)讀取我們?cè)趕etting.gradle中配置的多項(xiàng)目,引入并且進(jìn)行構(gòu)建,所以我們?cè)谧鼋M件化和模塊化的時(shí)候,經(jīng)常就是在setting.gradle里面做文章。

Project/RootProject/SubProject

每次構(gòu)建(build)至少由一個(gè)project構(gòu)成,我們每個(gè)build.gradle腳本在被Gradle解析后,都會(huì)生成一個(gè)Project對(duì)象。
而這里要講解下RootProject/SubProject的區(qū)別,其實(shí)也很好理解,上一篇博客其實(shí)就有提到,我們一個(gè)項(xiàng)目里有多個(gè)build.gradle,所以對(duì)應(yīng)的肯定有多個(gè)project對(duì)象,而根目錄下的build.gradle對(duì)應(yīng)的就是RootProject,每個(gè)子module下的build.gradle對(duì)應(yīng)的就是SubProject.我們可以輸入命令:./gradlew projects來(lái)查看當(dāng)前有的Project對(duì)象。

Root project 'StudyDemo'
\--- Project ':app'

關(guān)于Project相關(guān)的配置,后面會(huì)專(zhuān)門(mén)開(kāi)一篇博客進(jìn)行講解,本篇的博客的重點(diǎn)還是從代碼的角度,來(lái)了解下Gradle的實(shí)質(zhì)。

Task

每個(gè)task的實(shí)質(zhì)其實(shí)是一些更加細(xì)化的構(gòu)建(譬如編譯class、創(chuàng)建jar文件等)。所以Task正如其名,表示一個(gè)任務(wù),那我們用具體的代碼來(lái)看下Task具體是怎么樣的。

task hello {
    doLast {
        println 'Hello world!'
    }
}

當(dāng)我們?cè)赽uild.gradle中定義了上述Task后,我們通過(guò)./gradlew hello執(zhí)行task,就會(huì)發(fā)現(xiàn)輸出結(jié)果。

Task :hello
Hello world

Task的創(chuàng)建方式

而創(chuàng)建Task有很多種方式,這里列舉一下

//創(chuàng)建一個(gè)名為build.gradle的文件
task hello {
    doLast {
        println 'Hello world!'
    }
}

//這是快捷寫(xiě)法,用<<替換doLast
task hello2 << {
    println 'Hello world!'
}

task (hello3){
  println 'Hello3 world!'
}

task ('hello4'){
  println 'Hello4 world!'
}

tasks.create('hello5'){
  doLast{
    println 'Hello5 world!'
  }
}

Task的執(zhí)行階段

而這里我們繼續(xù)展開(kāi)下,從上面的幾種創(chuàng)建方式,我們會(huì)發(fā)現(xiàn)其實(shí)是有些區(qū)別的,為什么有些有doLast是什么,而有些沒(méi)有,有些有<<這樣的符號(hào),這種會(huì)有區(qū)別嗎?

task hello {
  println 'init here'
  doLast {
    println 'Hello world'
    }
}

通過(guò)上面這個(gè)例子我們來(lái)理解下,這里我們分別執(zhí)行兩個(gè)命令,第一個(gè)gradle -q會(huì)發(fā)現(xiàn)結(jié)果是這樣的。

init here
Welcome to Gradle 6.1.1.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
To see more detail about a task, run gradle help --task <task>
For troubleshooting, visit https://help.gradle.org

通過(guò)輸出的日志對(duì)比我們應(yīng)該會(huì)發(fā)現(xiàn)區(qū)別,我們?cè)跊](méi)有執(zhí)行task的時(shí)候,我們寫(xiě)入的init here同樣打印來(lái)出來(lái),而我們doLast中的邏輯并沒(méi)有執(zhí)行。
所以這里就可以看出區(qū)別,如果我們定義了一個(gè)Task沒(méi)有加doLast或者沒(méi)有使用<<的話(huà),task內(nèi)部的內(nèi)容無(wú)論執(zhí)行什么task都會(huì)在inittialization階段被執(zhí)行,而<<其實(shí)是doLast的簡(jiǎn)化,所以當(dāng)我們用doLast定義的內(nèi)容,則會(huì)在該Task被執(zhí)行的時(shí)候執(zhí)行。

自定義Task

Task其實(shí)就是一個(gè)對(duì)象,而且前面說(shuō)到了Groovy和Java是互通的,我們是可以自定義Task的,做一定程度的組合和封裝。

class MyTask extends DefaultTask {

    @TaskAction
    void action1(){
        println 'do action1'
    }
    
    @TaskAction
    void action2(){
        println 'do action2'
    }

    void action3(){
        println 'do action3'
    }
}

task hello3 (type:MyTask){
    doLast{
        println 'do Last'
    }
}

同樣我們來(lái)執(zhí)行下hello3 Tast,會(huì)得到下面的結(jié)果。

Task :hello3
do action1
do action2
do Last

所以這里就會(huì)明白,自定義Task我們首先需要集成DefaultTask對(duì)象,并且實(shí)現(xiàn)方法,方法使用@TaskAction進(jìn)行注解,被注解的方法會(huì)按照定義方法的順序在Task被執(zhí)行的時(shí)候執(zhí)行,而如果沒(méi)有使用注解,則就是一個(gè)常規(guī)的對(duì)象方法。我們可以看下@TaskAction的源碼。

/**
 * Marks a method as the action to run when the task is executed.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface TaskAction {
}

Task的順序

前面有說(shuō)到,Gradle通過(guò)將一系列的Task構(gòu)建成一個(gè)有向無(wú)環(huán)圖,來(lái)執(zhí)行最終的任務(wù),既然是有向無(wú)環(huán),那么說(shuō)明Task之間是有依賴(lài)和順序關(guān)系的。那么Task之間的依賴(lài)關(guān)系如何定義呢?這里就來(lái)介紹一下。

   doLast{
   }
   doFirst{
   }

首先看下Task內(nèi)部的一個(gè)API,用于表示Task內(nèi)部的執(zhí)行方式

  • doFirst:task執(zhí)行時(shí),最開(kāi)始的操作
  • doLast:task執(zhí)行時(shí),最后的操作
    接著來(lái)看下Task之間的順序如何控制。
1.dependsOn

最直接的一個(gè)任務(wù)依賴(lài)另一個(gè)任務(wù)的執(zhí)行就是通過(guò)denpendsOn方法。

task task1 << {
    println "我是task1----"
}

task task2 << {
    println "我是task2----"
}

//task2 依賴(lài) task1, 執(zhí)行task2之前先執(zhí)行task1
task2.dependsOn task1

執(zhí)行task2gradlew task2
結(jié)果

我是task1----
我是task2----

mustRunAfter

當(dāng)一種場(chǎng)景TaskA依賴(lài)TaskB和TaskC,但我們想控制TaskB和TaskC的關(guān)系,那么這時(shí)候我們?nèi)绾翁幚?,可能有剛才的介紹我們會(huì)用taskB.dependsOn TaskC,但是這樣其實(shí)就有問(wèn)題另,因?yàn)閷?shí)際上TaskB是不依賴(lài)TaskC的,只是在有TaskA的情況下,我們希望TaskC先執(zhí)行。這時(shí)候就需要用到mustRunAfter。

task taskA {
    doLast{
    println '我是taskA'
    }
}

task taskB {
    doLast{
    println '我是taskB'
    }
}

task taskC {
    doLast{
    println '我是taskC'
    }
}

taskA.dependsOn taskB
taskA.dependsOn taskC
taskB.mustRunAfter taskC

執(zhí)行TaskA,會(huì)得到結(jié)果

Task :taskC
我是taskC

Task :taskB
我是taskB

Task :taskA
我是taskA

所以mustRunAfter并不會(huì)添加依賴(lài),只是高度Gradle執(zhí)行的優(yōu)先級(jí),如果兩個(gè)Task同時(shí)存在,那么就會(huì)按照這個(gè)定義的優(yōu)先級(jí)執(zhí)行。

finalizedBy

我們?nèi)绻M蝿?wù)執(zhí)行結(jié)束的時(shí)候自動(dòng)執(zhí)行某個(gè)任務(wù),比如我們?cè)诖虬Y(jié)束后自動(dòng)上報(bào)包體積到后臺(tái),如果用dependsOn,那么我們就需要類(lèi)似打包任務(wù).dependsOn taskUpload,后續(xù)打包就需要通過(guò)taskUpload來(lái)執(zhí)行,這樣就很別扭,所以這里就有來(lái)finalizedBy,用于任務(wù)執(zhí)行結(jié)束后自動(dòng)執(zhí)行其他任務(wù)。

task taskC {
    doLast{
    println '我是taskC'
    }
}

task taskD {
    doLast{
    println '我是taskD'
    }
}

taskC.finalizedBy taskD

執(zhí)行taskC,會(huì)得到結(jié)果

Task :taskC
我是taskC

Task :taskD
我是taskD

常用Task

其實(shí)我們打開(kāi)Studio右邊的gradle面板就會(huì)看到很多的項(xiàng)目,里面的每一項(xiàng)其實(shí)就是一個(gè)Task。我們也可以用一個(gè)簡(jiǎn)單的方法來(lái)看下一個(gè)項(xiàng)目打包的時(shí)候會(huì)執(zhí)行的所有的Task,前面介紹到了我們會(huì)構(gòu)建一個(gè)Task的有向無(wú)環(huán)圖,所以我們可以等這個(gè)有向無(wú)環(huán)圖構(gòu)建成功的時(shí)候,將所有的task打印一下

gradle.taskGraph.beforeTask { Task task ->
    println "executing:  $task.name"
}

然后在命令行執(zhí)行./gradlew assemble | grep 'executing',具體命令后面會(huì)有個(gè)博客介紹一下,這里先簡(jiǎn)答說(shuō)下,assemble也是一個(gè)Task,我們?cè)赟tudio的操作面板中執(zhí)行打包編譯實(shí)質(zhì)就是執(zhí)行這個(gè)task,所有這里就相當(dāng)于執(zhí)行下這個(gè)打包的Task,然后用grep過(guò)濾下我們剛才打印的關(guān)鍵詞

executing:  preBuild
executing:  preDebugBuild
executing:  compileDebugAidl
executing:  checkDebugManifest
executing:  compileDebugRenderscript
executing:  generateDebugBuildConfig
executing:  mainApkListPersistenceDebug
executing:  generateDebugResValues
executing:  generateDebugResources
executing:  createDebugCompatibleScreenManifests
executing:  processDebugManifest
executing:  mergeDebugResources
executing:  processDebugResources
executing:  kaptGenerateStubsDebugKotlin
executing:  kaptDebugKotlin
executing:  compileDebugKotlin
executing:  mergeDebugShaders
executing:  compileDebugShaders
executing:  generateDebugAssets
executing:  mergeDebugAssets
executing:  javaPreCompileDebug
executing:  compileDebugJavaWithJavac
executing:  compileDebugSources
executing:  processDebugJavaRes
executing:  checkDebugDuplicateClasses
executing:  mergeDebugJavaResource
executing:  transformClassesWithDexBuilderForDebug
executing:  validateSigningDebug
executing:  signingConfigWriterDebug
executing:  mergeDebugJniLibFolders
executing:  extractProguardFiles
executing:  preReleaseBuild
executing:  compileReleaseAidl
executing:  compileReleaseRenderscript
executing:  checkReleaseManifest
executing:  generateReleaseBuildConfig
executing:  mainApkListPersistenceRelease
executing:  generateReleaseResValues
executing:  generateReleaseResources
executing:  mergeReleaseResources
executing:  createReleaseCompatibleScreenManifests
executing:  processReleaseManifest
executing:  processReleaseResources
executing:  kaptGenerateStubsReleaseKotlin
executing:  kaptReleaseKotlin
executing:  compileReleaseKotlin
executing:  javaPreCompileRelease
executing:  compileReleaseJavaWithJavac
executing:  compileReleaseSources
executing:  mergeDebugNativeLibs
executing:  stripDebugDebugSymbols
executing:  prepareLintJar
executing:  lintVitalRelease
executing:  mergeReleaseShaders
executing:  compileReleaseShaders
executing:  generateReleaseAssets
executing:  mergeReleaseAssets
executing:  signingConfigWriterRelease
executing:  mergeReleaseJniLibFolders
executing:  mergeReleaseNativeLibs
executing:  stripReleaseDebugSymbols
executing:  mergeReleaseGeneratedProguardFiles
executing:  processReleaseJavaRes
executing:  mergeReleaseJavaResource
executing:  transformClassesAndResourcesWithR8ForRelease
executing:  transformClassesAndDexWithShrinkResForRelease
executing:  packageRelease
executing:  assembleRelease
executing:  mergeExtDexDebug
executing:  mergeDexDebug
executing:  packageDebug
executing:  assembleDebug
executing:  assemble

后續(xù)的介紹也會(huì)圍繞這個(gè)進(jìn)行介紹,所以這里我先介紹下我認(rèn)為平時(shí)開(kāi)發(fā)中接觸到比較多的幾個(gè)Task吧

Task名稱(chēng) 作用
clean 清除緩存,懂的都懂吧~經(jīng)常用到
assemble 打包任務(wù)
install 安裝任務(wù),會(huì)安裝我們打出的包到手機(jī)

插件

其實(shí)我們了解到現(xiàn)在會(huì)發(fā)現(xiàn),其實(shí)Gradle的核心就是一個(gè)包含豐富語(yǔ)法糖,支持豐富DSL的流程控制框架,而框架的內(nèi)部其實(shí)是沒(méi)有實(shí)質(zhì)的操作的,所以Gradle的構(gòu)建便捷其實(shí)都是由插件提供支持的。插件可以看作是一系列Task的集合,插件添加了新的任務(wù),然后Gradle按照有向無(wú)環(huán)圖進(jìn)行順序執(zhí)行。在Gradle中插件一般分為兩種:

  • 腳本插件
    是額外的構(gòu)建腳本,他會(huì)進(jìn)一步配置構(gòu)建,我們可以理解我們將部分的配置抽取成一個(gè)腳本,然后進(jìn)行依賴(lài)。腳本插件通??梢詮谋镜匚募蛘哌h(yuǎn)程獲取,如果是從本地文件獲取則是相對(duì)于項(xiàng)目路徑,如果是遠(yuǎn)程獲取,則是由HTTP進(jìn)行指定。
    腳本插件其實(shí)并不能是一個(gè)真正的插件,他是腳本模塊化的基礎(chǔ),所以我們可以把復(fù)雜的腳本文件,進(jìn)行拆分,分段,拆分成一個(gè)職責(zé)分明的腳本插件。
  • 二進(jìn)制插件
    是實(shí)現(xiàn)了Plugin接口的類(lèi),并且采取編程的方式來(lái)操作構(gòu)建??梢岳斫馕覀冏远x一些需要在編譯執(zhí)行的時(shí)候來(lái)進(jìn)行一些自定義處理。

插件需要用過(guò)Project.apply()的方法聲明應(yīng)用,相同的插件可以應(yīng)用多次。

//腳本插件
apply from 'utils.gradle'
//二進(jìn)制插件
apply from 'java'

插件還可以使用插件ID,插件ID作為插件的唯一標(biāo)示,我們可以注冊(cè)的時(shí)候給插件定義一個(gè)唯一ID,后續(xù)講解插件開(kāi)發(fā)的時(shí)候會(huì)單獨(dú)講解。

gradle.properties

這個(gè)文件前面有提到這個(gè)文件,這個(gè)文件我們?cè)趧?chuàng)建項(xiàng)目的時(shí)候,AndroidStudio會(huì)自動(dòng)生成一個(gè)這個(gè)文件,這個(gè)文件是用來(lái)配置項(xiàng)目級(jí)別的Gradle配置,也就是我們可以在這個(gè)文件里配置項(xiàng)目級(jí)別的公共屬性。
當(dāng)然這個(gè)文件是可以有多個(gè)的,其中子項(xiàng)目的gradle.properties會(huì)覆蓋rootProject的gradle.properties,但是子項(xiàng)目的的gradle.properties屬性只會(huì)在子項(xiàng)目中可見(jiàn),只有rootProject的gradle.properties的屬性是全局可見(jiàn)的。

//gradle.properties
COMPILE_SDK_VERSION=28
MIN_SDK_VERSION=15

//setting.gradle
// 輸出Gradle對(duì)象的一些信息
def printGradleInfo(){
    println "COMPILE_SDK_VERSION:" + COMPILE_SDK_VERSION
}

printGradleInfo()

依賴(lài)

gralde之所以能代替Maven,其依賴(lài)管理是一大關(guān)鍵因素。這里先簡(jiǎn)單介紹下依賴(lài)的概念,后面會(huì)專(zhuān)門(mén)開(kāi)一篇博客講解依賴(lài)的相關(guān)內(nèi)容。
首先,Gradle做為一個(gè)項(xiàng)目構(gòu)建的DSL工程,Gradle需要知道項(xiàng)目構(gòu)建時(shí)需要的一些文件,而這些文件往往除了我們自己編寫(xiě)的項(xiàng)目文件,往往還會(huì)用到其他工程的一些文件,例如三方的開(kāi)源庫(kù),自己編寫(xiě)的子項(xiàng)目,這些文件就是項(xiàng)目的依賴(lài),Gradle在項(xiàng)目構(gòu)建的時(shí)候需要告訴它項(xiàng)目的依賴(lài)是什么,在哪里能夠找到他們,然后幫你加入到構(gòu)建中,有的可能是在本地,有的可能需要到遠(yuǎn)端下載,有的甚至是另一個(gè)工程。我們先簡(jiǎn)單的看一個(gè)依賴(lài)。

apply plugin: 'java'
repositories {
    mavenCentral()
    google()
    jcenter()
}
dependencies {
  implementation 'androidx.appcompat:appcompat:1.0.2'
}

首先我們?cè)?code>dependencies中聲明來(lái)我們需要的依賴(lài)庫(kù),也就是androidx.appcompat:appcompat:1.0.2,這里往往是group:name:version三段式組成,然后gradle在編譯的時(shí)候知道我們項(xiàng)目的構(gòu)建需要這個(gè)庫(kù)的依賴(lài)支持,那么肯定需要找到這個(gè)庫(kù),這時(shí)候就會(huì)去repositories中尋找,這里看到我們聲明了三個(gè)地址分別是maven、google、jcenter倉(cāng)庫(kù)。這樣在構(gòu)建的時(shí)候就會(huì)從這個(gè)倉(cāng)庫(kù)地址中下載這個(gè)依賴(lài)庫(kù),然后進(jìn)行構(gòu)建。

總結(jié)

這篇博客可能稍微講的有點(diǎn)亂,Gradle是一個(gè)龐大的體系,里面的每一個(gè)點(diǎn)都可以展開(kāi)講解,細(xì)枝末節(jié)很多,所以這篇博客還是篇前期的基礎(chǔ)概念講解,后面會(huì)針對(duì)這里面的接觸概念進(jìn)行專(zhuān)項(xiàng)拓展講解。希望自己的這篇Gradle系列能幫助大家把Gradle弄懂吃透。

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

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

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