系列目錄
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)知。本篇博客開始就開始進(jìn)入Gradle的學(xué)習(xí)了,本篇博客將從Gradle的基礎(chǔ)概念進(jìn)行講解,Gradle的基礎(chǔ)概念又分為Gradle和AGP(Android Gradle Plugin后續(xù)簡(jiǎn)稱AGP)的基礎(chǔ)概念,所以這里會(huì)做一定的劃分,可能有的概念在兩邊都有涉及,所以區(qū)分不會(huì)特別明顯,但是對(duì)于我們學(xué)習(xí)來(lái)說(shuō),并不會(huì)造成什么困擾。
Gradle基礎(chǔ)概念
首先還是回到上篇博客一直圍繞的一個(gè)話題,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 可以類比做一條流水線,task 可以比作流水線上的機(jī)器人,每個(gè)機(jī)器人負(fù)責(zé)不同的事情,最終生成完整的構(gòu)建產(chǎn)物。
而Gradle的代碼實(shí)質(zhì)是配置腳本,執(zhí)行一種類型的配置腳本時(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在后面再專門講解。
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ì)專門開一篇博客進(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!'
}
}
//這是快捷寫法,用<<替換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ù)展開下,從上面的幾種創(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í)候,我們寫入的init here同樣打印來(lái)出來(lái),而我們doLast
中的邏輯并沒(méi)有執(zhí)行。
所以這里就可以看出區(qū)別,如果我們定義了一個(gè)Task沒(méi)有加doLast
或者沒(méi)有使用<<
的話,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之間是有依賴和順序關(guān)系的。那么Task之間的依賴關(guān)系如何定義呢?這里就來(lái)介紹一下。
doLast{
}
doFirst{
}
首先看下Task內(nèi)部的一個(gè)API,用于表示Task內(nèi)部的執(zhí)行方式
- doFirst:task執(zhí)行時(shí),最開始的操作
- doLast:task執(zhí)行時(shí),最后的操作
接著來(lái)看下Task之間的順序如何控制。
1.dependsOn
最直接的一個(gè)任務(wù)依賴另一個(gè)任務(wù)的執(zhí)行就是通過(guò)denpendsOn
方法。
task task1 << {
println "我是task1----"
}
task task2 << {
println "我是task2----"
}
//task2 依賴 task1, 執(zhí)行task2之前先執(zhí)行task1
task2.dependsOn task1
執(zhí)行task2gradlew task2
結(jié)果
我是task1----
我是task2----
mustRunAfter
當(dāng)一種場(chǎng)景TaskA依賴TaskB和TaskC,但我們想控制TaskB和TaskC的關(guān)系,那么這時(shí)候我們?nèi)绾翁幚恚赡苡袆偛诺慕榻B我們會(huì)用taskB.dependsOn TaskC,但是這樣其實(shí)就有問(wèn)題另,因?yàn)閷?shí)際上TaskB是不依賴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ì)添加依賴,只是高度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,那么我們就需要類似打包任務(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í)我們打開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í)開發(fā)中接觸到比較多的幾個(gè)Task吧
Task名稱 | 作用 |
---|---|
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)行依賴。腳本插件通常可以從本地文件或者遠(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)操作構(gòu)建。可以理解我們自定義一些需要在編譯執(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ù)講解插件開發(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)目中可見,只有rootProject的gradle.properties的屬性是全局可見的。
//gradle.properties
COMPILE_SDK_VERSION=28
MIN_SDK_VERSION=15
//setting.gradle
// 輸出Gradle對(duì)象的一些信息
def printGradleInfo(){
println "COMPILE_SDK_VERSION:" + COMPILE_SDK_VERSION
}
printGradleInfo()
依賴
gralde之所以能代替Maven,其依賴管理是一大關(guān)鍵因素。這里先簡(jiǎn)單介紹下依賴的概念,后面會(huì)專門開一篇博客講解依賴的相關(guān)內(nèi)容。
首先,Gradle做為一個(gè)項(xiàng)目構(gòu)建的DSL工程,Gradle需要知道項(xiàng)目構(gòu)建時(shí)需要的一些文件,而這些文件往往除了我們自己編寫的項(xiàng)目文件,往往還會(huì)用到其他工程的一些文件,例如三方的開源庫(kù),自己編寫的子項(xiàng)目,這些文件就是項(xiàng)目的依賴,Gradle在項(xiàng)目構(gòu)建的時(shí)候需要告訴它項(xiàng)目的依賴是什么,在哪里能夠找到他們,然后幫你加入到構(gòu)建中,有的可能是在本地,有的可能需要到遠(yuǎn)端下載,有的甚至是另一個(gè)工程。我們先簡(jiǎn)單的看一個(gè)依賴。
apply plugin: 'java'
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
}
首先我們?cè)?code>dependencies中聲明來(lái)我們需要的依賴庫(kù),也就是androidx.appcompat:appcompat:1.0.2
,這里往往是group:name:version
三段式組成,然后gradle在編譯的時(shí)候知道我們項(xiàng)目的構(gòu)建需要這個(gè)庫(kù)的依賴支持,那么肯定需要找到這個(gè)庫(kù),這時(shí)候就會(huì)去repositories
中尋找,這里看到我們聲明了三個(gè)地址分別是maven、google、jcenter倉(cāng)庫(kù)。這樣在構(gòu)建的時(shí)候就會(huì)從這個(gè)倉(cāng)庫(kù)地址中下載這個(gè)依賴庫(kù),然后進(jìn)行構(gòu)建。
總結(jié)
這篇博客可能稍微講的有點(diǎn)亂,Gradle是一個(gè)龐大的體系,里面的每一個(gè)點(diǎn)都可以展開講解,細(xì)枝末節(jié)很多,所以這篇博客還是篇前期的基礎(chǔ)概念講解,后面會(huì)針對(duì)這里面的接觸概念進(jìn)行專項(xiàng)拓展講解。希望自己的這篇Gradle系列能幫助大家把Gradle弄懂吃透。