Gradle插件從入門到進階

Gradle插件從入門到進階

首發於個人blog xsfelvis.github.io/2019/05/02/…html

Transform相關請參考 xsfelvis.github.io/2019/05/02/…java

一、簡介

Gradle自己的領域對象主要有Project和Task。Project爲Task提供了執行上下文,全部的Plugin要麼向Project中添加用於配置的Property,要麼向Project中添加不一樣的Task。一個Task表示一個邏輯上較爲獨立的執行過程,好比編譯Java源代碼,拷貝文件,打包Jar文件,甚至能夠是執行一個系統命令或者調用Ant。另外,一個Task能夠讀取和設置Project的Property以完成特定的操做。 android

Groovy 基礎

Android DSL 基礎

相關代碼在 github.com/xsfelvis/Gr…android-studio

二、核心概念

Project對象

自定義插件類是經過實現Plugin 接口,並將 org.gradle.api.Project做爲模板參數,其中org.gradle.api.Project的實例對象將做爲參數傳給void apply(Project project)函數,根據官網,能夠看出Project是與Gradle交互的主接口,經過Project可使用gradle的全部特性,而且 Project與build.grale是一對一的關係。簡而言之,就是經過代碼使用Gradle,經過Project這個入口便可緩存

咱們對project的理解更多來源於項目目錄中的build.gradle文件(由於它其實就是project對象的委託,在腳本中的配置方法都對應着Project中的API,當構建進程啓動後Gradle基於build.gradle中的配置實例化org.gradle.api.Project類,本質上能夠認爲是包含多個Task的容器,全部的Task都存放在TaskContainer中,Project對象的類圖以下所示:bash

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

Gradle Task API

Gradle構建腳本默認的名字是build.gradle,當在shell中執行gradle命令時,Gradle會去當前目錄下尋找名字是build.gradle的文件。在Gradle中一個原子性的操做叫作task,簡單理解爲task是Gradle腳本中的最小可執行單元。

下面是task的類圖。

image.png

Task的Actions

一個Task是由一序列Action組成的,當運行一個Task的時候,這個Task裏的Action序列會按照順序執行

Task的幾種常見寫法

task myTask1 {
    doLast {
        println "doLast in task1"
    }
}

task myTask2 << {
    println "doLast in task2"
}

//採用 Project.task(String name) 方法來建立
project.task("myTask3").doLast {
    println "doLast in task3"
}

//採用 TaskContainer.create(String name) 方法來建立
project.tasks.create("myTask4").doLast {
    println "doLast in task4"
}

project.tasks.create("myTask5") << {
    println "doLast in task5"
}




複製代碼

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

  • doFirst  等價操做 縮寫 leftShift <<(5.0會廢棄)
  • doLast

在 Gradle 中定義 Task 的時候,能夠指定更多的參數,以下所示:

參數名 含義 默認值
name task的名字 必須指定,不能爲空
type task的父類 默認值爲org.gradle.api.DefaultTask
overwrite 是否替換已經存在的同名task false
group task所屬的分組名 null
description task的描述 null
dependsOn task依賴的task集合
constructorArgs 構造函數參數

Task的依賴

gradle中任務的執行順序是不肯定的。經過task之間的依賴關係,gradle可以確保所依賴的task會被當前的task先執行。使用task的dependsOn()方法,容許咱們爲task聲明一個或者多個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 :app:first 


> Task :app:second 


> Task :app:test1 


> Task :app:third 
複製代碼

Task的類型

有copy、jar、Delete 等等 能夠參考Doc文檔

task copyFile(type: Copy) {
   from 'xml'
   into 'destination'
}

自定義Task

Gradle 中經過 task 關鍵字建立的 task,默認的父類都是 org.gradle.api.DefaultTask,這裏定義了一些 task 的默認行爲。看看下面這個例子:

//自定義Task類,必須繼承自DefaultTask
class SayHelloTask extends DefaultTask {
    
    String msg = "default name"
    int age = 18        

    //構造函數必須用@javax.inject.Inject註解標識
    @javax.inject.Inject
    SayHelloTask(int age) {
        this.age = age
    }

    //經過@TaskAction註解來標識該Task要執行的動做
    @TaskAction
    void sayHello() {
        println "Hello $msg ! age is ${age}"
    }

}

//經過constructorArgs參數來指定構造函數的參數值
task hello1(type: SayHelloTask, constructorArgs: [30])

//經過type參數指定task的父類,能夠在配置代碼裏修改父類的屬性
task hello2(type: SayHelloTask, constructorArgs: [18]) {
        //配置代碼裏修改 SayHelloTask 裏的字段 msg 的值
    msg = "hjy"
}
複製代碼


執行這兩個task

> Task :hello1
Hello default name ! age is 30

> Task :hello2
Hello hjy ! age is 18
複製代碼

Task的類圖

Gradle所說的Task是org.gradle.api.Task接口,默認實現是org.gradle.api.DefaultTask類,其類圖以下

image.png

TaskContainer接口解析

TaskContianer 是用來管理全部的 Task 實例集合的,能夠經過 Project.getTasks() 來獲取 TaskContainer 實例。

org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

//建立task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task

//當task被加入到TaskContainer時的監聽
whenTaskAdded(action: Closure)


//當有task建立時
getTasks().whenTaskAdded { Task task ->
    println "The task ${task.getName()} is added to the TaskContainer"
}

//採用create(name: String)建立
getTasks().create("task1")

//採用create(options: Map<String, ?>)建立
getTasks().create([name: "task2", group: "MyGroup", description: "這是task2描述", dependsOn: ["task1"]])

//採用create(options: Map<String, ?>, configure: Closure)建立
getTasks().create("task3", {
    group "MyGroup"
    setDependsOn(["task1", "task2"])
    setDescription "這是task3描述"
})

複製代碼

默認狀況下,咱們常見的task都是org.gradle.api.DefaultTask類型。可是在gradle當中有至關豐富的task類型咱們能夠直接使用。要更改task的類型,咱們能夠參考下面的示例

task createDistribution(type:Zip){
    
}
複製代碼

更多關於task的類型,能夠參考gradle的官方文檔

www.jianshu.com/p/cd1a78dc8…
www.ezlippi.com/blog/2015/0…
blog.csdn.net/lzyzsd/arti…

Task的增量構建

Gradle 支持一種叫作 up-to-date 檢查的功能,也就是常說的增量構建。Gradle 的 Task 會把每次運行的結果緩存下來,當下次運行時,會檢查輸出結果有沒有變動,若是沒有變動則跳過運行,這樣能夠提升 Gradle 的構建速度。
一般,一個 task 會有一些輸入(inputs)和一些輸出(outputs),task 的輸入會影響其輸出結果,以官網中的一張圖爲例:

image.png

圖中表示一個java編譯的task,它的輸入有2種,一是JDK版本號,一是源文件,它的輸出結果爲class文件,只要JSK版本號與源文件有任何變更,最終編譯出的class文件確定不一樣的。當咱們執行過一次·編譯任務以後,再次運行該task,若是發現他的輸入沒有任何改動,那麼它編譯後的結果確定也是不變的,能夠直接從緩存裏獲取輸出,這樣Gradle會標識該task爲UP-TO-DATE,從而跳過該task的執行

TaskInputs、TaskOutputs介紹

如何實現一個增量構建呢,至少指定一個輸入,一個輸出,Task.getInputs() 對象類型爲 TaskInputs,Task.getOutputs() 對象類型爲 TaskOuputs,從中也能夠看到inputs、outputs都支持哪些數據類型

task test1 {
    //設置inputs
    inputs.property("name", "hjy")
    inputs.property("age", 20)
    //設置outputs
    outputs.file("$buildDir/test.txt")

    doLast {
        println "exec task task1"
    }
}

task test2 {
    doLast {
        println "exec task task2"
    }
}

//第一次的運行結果
> Task :test1
exec task task1

> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

//第二次的運行結果
> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date
複製代碼

從結果中能夠看到,第2次運行時,test1 task 並無運行,而是被標記爲 up-to-date,而 test2 task 則每次都會運行,這就是典型的增量構建。

taskInputs、taskOutputs註解

能夠經過task註解來實現增量構建,這是一種更加靈活方便的方式

註解名 屬性類型 描述
@Input 任意Serializable類型 一個簡單的輸入值
@InputFile File 一個輸入文件,不是目錄
@InputDirectory File 一個輸入目錄,不是文件
@InputFiles Iterable File列表,包含文件和目錄
@OutputFile File 一個輸出文件,不是目錄
@OutputDirectory File 一個輸出目錄,不是文件
@OutputFiles Map<String, File>或Iterable 輸出文件列表
@OutputDirectories Map<String, File>或Iterable 輸出目錄列表
class SayHelloTask extends DefaultTask {
    
    //定義輸入
    @Input
    String username;
    @Input
    int age

    //定義輸出
    @OutputDirectory
    File destDir;

    @TaskAction
    void sayHello() {
        println "Hello $username ! age is $age"
    }

}

task test(type: SayHelloTask) {
    age = 18
    username = "hjy"
    destDir = file("$buildDir/test")
}
複製代碼

Property

ext命名空間

Gradle中不少模型類都提供了特別的屬性支持,好比Project.在gradle內部,這些屬性會以鍵值對的形式存儲。使用ext命名空間,咱們能夠方便的添加屬性。下面的方式都是支持的:

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

值得注意的是,只有在聲明屬性的時候咱們須要使用ext命名空間,在使用屬性的時候,ext命名空間是能夠省略的。

屬性文件

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

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

有的時候,咱們可能須要在代碼中動態的建立屬性文件並讀取文件中的屬性(好比自定義插件的時候),咱們可使用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)
        }
    }
}
複製代碼

關於屬性很重要的一點是屬性是能夠繼承的。在一個項目中定義的屬性會自動的被其子項目繼承,無論咱們是用以上哪一種方式添加屬性都是適用的。

ExtensionContainer

Extension簡介

就是 Gradle 的 Extension,翻譯成中文意思就叫擴展。它的做用就是經過實現自定義的 Extension,能夠在 Gradle 腳本中增長相似 android 這樣命名空間的配置,Gradle 能夠識別這種配置,並讀取裏面的配置內容。

通常咱們經過ExtensionContainer來建立Extension,這個類跟TaskContainer命名有點相似。TaskContainer是用來建立並管理Task的,而ExtensionContainer則是用來建立並管理Extension的,經過Project的如下API能夠獲取到ExtensionContainer對象

ExtensionContainer getExtensions()
複製代碼

簡單的Extension

/先定義一個普通的java類,包含2個屬性
class Foo {
    int age
    String username
    String toString() {
        return "name = ${username}, age = ${age}"
    }
}
//建立一個名爲 foo 的Extension
getExtensions().create("foo", Foo)
//配置Extension
foo {
    age = 30
    username = "hjy"
}
task testExt.doLast {
    //能直接經過 project 獲取到自定義的 Extension
    println project.foo
}
複製代碼

foo 就是咱們自定義的 Extension 了,它裏面能配置的屬性與類 Foo 中的字段是一致的,在 build.gradle 中能夠直接經過 project.foo 來訪問。每一個 Extension 實際上與某個類是相關聯的,在 build.gradle 中經過 DSL 來定義,Gradle 會識別解析並生成一個對象實例,經過該類能夠獲取咱們所配置的信息。
 Project 有個擴展屬性是經過 ext 命名空間配置的,能夠看到 ext 與這裏是相似的,不一樣的是 ext 能夠配置任何鍵值對的屬性值,而這裏只能識別咱們定義的 Java 類裏的屬性值。

ExtensionContainer主要api及用法

<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)
複製代碼

先來看看後面這個 API 全部參數的含義。

  • publicType:建立的 Extension 實例暴露出來的類類型;
  • name:要建立的Extension的名字,能夠是任意符合命名規則的字符串,不能與已有的重複,不然會拋異常;
  • instanceType:該Extension的類類型;
  • constructionArguments:類的構造函數參數值

官方文檔裏還說明了一個特性,建立的 Extension 對象都默認實現了 ExtensionAware 接口,並註明出處。

示例

//父類
class Animal {
    
    String username
    int legs

    Animal(String name) {
        username = name
    }
    
    void setLegs(int c) {
        legs = c
    }

    String toString() {
        return "This animal is $username, it has ${legs} legs."
    }
}

//子類
class Pig extends Animal {
    
    int age
    String owner

    Pig(int age, String owner) {
        super("Pig")
        this.age = age
        this.owner = owner
    }

    String toString() {
        return super.toString() + " Its age is $age, its owner is $owner."
    }

}

//建立的Extension是 暴露出來Animal 類型,建立extension名稱是name,該extension的類型是Pig,後面2個是參數
Animal aAnimal = getExtensions().create(Animal, "animal", Pig, 3, "hjy")
//建立的Extension是 Pig 類型
Pig aPig = getExtensions().create("pig", Pig, 5, "kobe")

animal {
    legs = 4    //配置屬性
}

pig {
    setLegs 2   //這個是方法調用,也就是 setLegs(2)
}

task testExt << {
    println aAnimal
    println aPig
    //驗證 aPig 對象是 ExtensionAware 類型的
    println "aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}
複製代碼

增長Extension

  • create() 方法會建立並返回一個 Extension 對象,
  • add() 方法,惟一的差異是它並不會返回一個 Extension 對象

基於前面的這個實例,咱們能夠換一種寫法以下:

getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
    username = "MyPig"
    legs = 4
    age = 1
}
task testExt << {
    def aPig = project.getExtensions().getByName("mypig")
    println aPig
}
複製代碼

查找Extension

Object findByName(String name)
<T> T findByType(Class<T> type)
Object getByName(String name)       //找不到會拋異常
<T> T getByType(Class<T> type)  //找不到會拋異常
複製代碼

嵌套Extension 方式一

相似下面這樣的配置應該隨處可見:

outer {
    
    outerName "outer"
    msg "this is a outer message."
    inner {
        innerName "inner"
        msg "This is a inner message."
    }
    
}
複製代碼

能夠經過下面的方式來建立

class OuterExt {
    
    String outerName
    String msg
    InnerExt innerExt = new InnerExt()

    void outerName(String name) {
        outerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }
    
    //建立內部Extension,名稱爲方法名 inner
    void inner(Action<InnerExt> action) {
        action.execute(inner)
    }

    //建立內部Extension,名稱爲方法名 inner
    void inner(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, innerExt) 
    }

    String toString() {
        return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
    }

}


class InnerExt {
    
    String innerName String msg void innerName(String name) {
        innerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }

    String toString() {
        return "InnerExt[ name = ${innerName}, msg = ${msg}]"
    }

}

def outExt = getExtensions().create("outer", OuterExt)

outer {
    
    outerName "outer"
    msg "this is a outer message."

    inner {
        innerName "inner"
        msg "This is a inner message."
    }

}

task testExt doLast {
    println outExt
}
複製代碼

關鍵在如下下面的方法

void inner(Action<InnerExt> action)
void inner(Closure c)
複製代碼

定義在outer內部的inner,Gradle 解析時本質上會調用 outer.inner(……)方法,該方法的參數是一個閉包(Script Block) 因此在類OuterExt中必須定義inner方法

嵌套Extension方式二(NamedDomainObjectContainer)

使用場景

Gradle Extension 的時候,說到名爲 android 的 Extension 是由 BaseExtension 這個類來實現的,裏面對 buildTypes 是這樣定義的:

private final NamedDomainObjectContainer<BuildType> buildTypes;
複製代碼

buildTypes 就是 NamedDomainObjectContainer 類型的,先來看看 buildTypes 在 Android 中是怎麼使用的,下面這段代碼應該都很熟悉了,它定義了 debug、relase 兩種打包模式:

android {
    buildTypes {
        release {
            // 是否開啓混淆
            minifyEnabled true
            // 開啓ZipAlign優化
            zipAlignEnabled true
            //去掉不用資源
            shrinkResources true
            // 混淆文件位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // 使用release簽名
            signingConfig signingConfigs.hmiou
        }
        debug {
            signingConfig signingConfigs.hmiou
        }
    }
}
複製代碼

當咱們新建一個項目時候,默認會有debug和release這2個配置,那麼debug、release能夠修改其餘名字嗎,能增長其餘名字來配置嗎,好比想增長一個測試包配置test,還有就是release裏面都能配置哪些屬性呢
我來講下結果,若是不肯定的,能夠實際驗證一下:

  • debug、release 是能夠修改爲其餘名字的,你能夠替換成你喜歡的名字;
  • 你能夠增長任意不一樣名字的配置,好比增長一個開發版本的打包配置 dev ;
  • 可配置的屬性可參考接口:com.android.builder.model.BuildType ;

能夠看到它是很是靈活的,能夠根據不一樣的場景定義不一樣的配置,每一個不一樣的命名空間都會生成一個 BuildType 配置。要實現這樣的功能,必須使用 NamedDomainObjectContainer 類型。

什麼是NamedDomainObjectContainer

顧名思義就是命名領域對象容器,它的主要功能有:

  • 經過DSL建立指定type的對象實例
  • 指定的type必須有一個public構造函數,且必須帶有一個String name的參數
  • 它是一個實現了SortedSet接口的容器,因此全部領域對象的name屬性都必須是惟一的,在容器內部會用name屬性來排序

named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type.  Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.

建立NamedDomainObjectContainer

NamedDomainObjectContainer 須要經過 Project.container(...) API 來建立,其定義爲:

<T> NamedDomainObjectContainer<T> container(Class<T> type)
<T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory)
<T> NamedDomainObjectContainer<T> container(java.lang.Class<T> type, Closure factoryClosure
複製代碼

來看個具體的實例:

//這是領域對象類型定義
class TestDomainObj {
    
    //必須定義一個 name 屬性,而且這個屬性值初始化之後不要修改
    String name

    String msg

    //構造函數必須有一個 name 參數
    public TestDomainObj(String name) {
        this.name = name
    }

    void msg(String msg) {
        this.msg = msg
    }

    String toString() {
        return "name = ${name}, msg = ${msg}"
    }
}

//建立一個擴展
class TestExtension {

    //定義一個 NamedDomainObjectContainer 屬性
    NamedDomainObjectContainer<TestDomainObj> testDomains

    public TestExtension(Project project) {
        //經過 project.container(...) 方法建立 NamedDomainObjectContainer 
        NamedDomainObjectContainer<TestDomainObj> domainObjs = project.container(TestDomainObj)
        testDomains = domainObjs
    }

    //讓其支持 Gradle DSL 語法
    void testDomain(Action<NamedDomainObjectContainer<TestDomainObj>> action) {
        action.execute(testDomains)
    }

    void test() {
        //遍歷命名領域對象容器,打印出全部的領域對象值
        testDomains.all { data ->
            println data        
        }
    }
}

//建立一個名爲 test 的 Extension
def testExt = getExtensions().create("test", TestExtension, project)

test {
    testDomain {
        domain2 {
            msg "This is domain2"
        }
        domain1 {
            msg "This is domain1"
        }
        domain3 {
            msg "This is domain3"
        }
    }   
}

task myTask doLast {
    testExt.test()
}
複製代碼

運行結果以下:

name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3
複製代碼

查找和遍歷

NamedDomainObjectContainer 既然是一個容器類,與之相應的必然會有查找容器裏的元素和遍歷容器的方法:

//遍歷
void all(Closure action)
//查找
<T> T getByName(String name)
//查找
<T> T findByName(String name)
複製代碼

仍是接着前面的例子:

//經過名字查找
TestDomainObj testData = testDomains.getByName("domain2")
println "getByName: ${testData}"
//遍歷命名領域對象容器,打印出全部的領域對象值
testDomains.all { data ->
    println data        
}
複製代碼

須要注意的是,Gradle 中有不少容器類的迭代遍歷方法有 each(Closure action)、all(Closure action),可是通常咱們都會用 all(...) 來進行容器的迭代。all(...) 迭代方法的特別之處是,不論是容器內已存在的元素,仍是後續任什麼時候刻加進去的元素,都會進行遍歷。

Android的Extension

咱們在gradle中會看到 android{}

image.png

defaultConfig、productFlavors、signingConfigs、buildTypes 這4個內部 Extension對象是怎麼定義的,經過查看源碼能夠找到一個叫 BaseExtension 的類,裏面的相關代碼以下:

private final DefaultConfig defaultConfig;
    private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
    private final NamedDomainObjectContainer<BuildType> buildTypes;
    private final NamedDomainObjectContainer<SigningConfig> signingConfigs;
    public void defaultConfig(Action<DefaultConfig> action) {
        this.checkWritability();
        action.execute(this.defaultConfig);
    }
    
     public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        this.checkWritability();
        action.execute(this.buildTypes);
    }
    public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
        this.checkWritability();
        action.execute(this.productFlavors);
    }
    public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
        this.checkWritability();
        action.execute(this.signingConfigs);
    }
複製代碼

在 app 的 build.gradle 裏咱們一般會採用插件 apply plugin: 'com.android.application' ,而在 library module 中則採用插件 apply plugin: 'com.android.library',AppPlugin 就是插件 com.android.application 的實現類,LibraryPlugin 則是插件 com.android.library 的實現類,接着再看看 AppPlugin 裏是怎樣建立 Extension 的:

public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry);
    }
    protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
        return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
    }
    public void apply(Project project) {
        super.apply(project);
    }
    //省略...
}
複製代碼

在 createExtension() 方法中,能夠看到建立了一個名爲 android 的 Extension,該 Extension 的類型爲 AppExtension,而 AppExtension 的繼承結構爲 AppExtension -> TestedExtension -> BaseExtension,因此它的實現邏輯大部分都是在 BaseExtension 裏實現的。

在Android 工程中的build.gradle 文件中,咱們配置相關信息使用 android{} 節點,從 AppPlugin 也能看出其 Extension的名稱爲 android ,因此獲取方法以下:

  • project.extensions.getByName
  • project.extensions.getByType

def getInfo() {
		//或者 直接 project.android
    BaseExtension extension = project.extensions.getByName("android")
    def android = project.extensions.getByType(AppExtension)
    project.android
    
    println "buildToolsVersion:${extension.buildToolsVersion}"
    println "compileSdkVersion:${extension.getCompileSdkVersion()}"
    println "applicationId:${extension.defaultConfig.applicationId}"
    println "minSdkVersion:${extension.defaultConfig.minSdkVersion}"
    println "targetSdkVersion:${extension.defaultConfig.targetSdkVersion}"
    println "versionCode:${extension.defaultConfig.versionCode}"
    println "versionName:${extension.defaultConfig.versionName}"
}
複製代碼

更詳細的請參考

三、構建生命週期

三個階段

每次構建的本質其實就是執行一系列的Task,某些Task可能依賴其餘Task,那些沒有依賴的Task總會被最早執行,並且每一個Task只會被執行一遍,每次構建的依賴關係是在構建的配置階段肯定的,在gradle構建中,構建的生命週期主要包括如下三個階段:

初始化(Initialization)

構建工具會根據每一個build.gradle文件建立出一個Project實例,初始化階段會執行項目根目錄下的Settings.gradle文件,來分析哪些項目參與構建

include ':app'

include ':libraries:someProject'

配置(Configuration)

這個階段經過執行構建腳原本爲每一個project建立並分配Task。配置階段會去加載全部參與構建的項目的build.gradle文件,會將build.gradle文件實例化爲一個Gradle的project對象,而後分析project之間的依賴關係,下載依賴文件,分析project下的task之間的依賴關係

執行(Execution)

這是Task真正被執行的階段,Gradle會根據依賴關係決定哪些Task須要被執行,以及執行的前後順序。
task是Gradle中的最小執行單元,咱們全部的構建,編譯,打包,debug,test等都是執行了某一個task,一個project能夠有多個task,task之間能夠互相依賴。例如我有兩個task,taskA和taskB,指定taskA依賴taskB,而後執行taskA,這時會先去執行taskB,taskB執行完畢後在執行taskA。

image.png

在根目錄和app目錄下的build.gradle中會引用下面的插件

dependencies { classpath 'com.android.tools.build:gradle:2.2.2' }

apply plugin: 'com.android.application'

image.png

Android 三個文件重要的gradle

Gradle項目有3個重要的文件須要深刻理解:

  • settings.gradle

settings.gradle 文件會在構建的 initialization 階段被執行,它用於告訴構建系統哪些模塊須要包含到構建過程當中。對於單模塊項目, settings.gradle 文件不是必需的。對於多模塊項目,若是沒有該文件,構建系統就不能知道該用到哪些模塊。

  • 項目根目錄的 build.gradle

項目根目錄的 build.gradle 文件用來配置針對全部模塊的一些屬性。它默認包含2個代碼塊:buildscript{…}和allprojects{…}。前者用於配置構建腳本所用到的代碼庫和依賴關係,後者用於定義全部模塊須要用到的一些公共屬性。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.2'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

複製代碼

buildscript:定義了 Android 編譯工具的類路徑。repositories中, jCenter是一個著名的 Maven 倉庫。
allprojects:中定義的屬性會被應用到全部 moudle 中,可是爲了保證每一個項目的獨立性,咱們通常不會在這裏面操做太多共有的東西。

  • 模塊目錄的 build.gradle

模塊級配置文件 build.gradle 針對每一個moudle 的配置,若是這裏的定義的選項和頂層 build.gradle定義的相同。它有3個重要的代碼塊:plugin,android 和 dependencies。

經常使用gradle命令

//構建
gradlew app:clean    //移除全部的編譯輸出文件,好比apk

gradlew app:build   //構建 app module ,構建任務,至關於同時執行了check任務和assemble任務

//檢測
gradlew app:check   //執行lint檢測編譯。

//打包
gradlew app:assemble //能夠編譯出release包和debug包,可使用gradlew assembleRelease或者gradlew assembleDebug來單獨編譯一種包

gradlew app:assembleRelease  //app module 打 release 包

gradlew app:assembleDebug  //app module 打 debug 包

//安裝,卸載

gradlew app:installDebug  //安裝 app 的 debug 包到手機上

gradlew app:uninstallDebug  //卸載手機上 app 的 debug 包

gradlew app:uninstallRelease  //卸載手機上 app 的 release 包

gradlew app:uninstallAll  //卸載手機上全部 app 的包

複製代碼

監聽生命週期

在gradle的構建過程當中,gradle爲咱們提供了很是豐富的鉤子,幫助咱們針對項目的需求定製構建的邏輯,以下圖所示:

image.png

要監聽這些生命週期,主要有兩種方式:

  • 添加監聽器
  • 使用鉤子的配置塊

關於可用的鉤子能夠參考GradleProject中的定義,經常使用的鉤子包括:

Project

Project提供的生命週期回調方法有

//在 Project 進行配置前調用
void beforeEvaluate(Closure closure)
//在 Project 配置結束後調用
void afterEvaluate(Closure closure)
複製代碼

beforeEvaluate 必須在父模塊的 build.gradle 對子模塊進行配置才能生效,由於在當前模塊的 build.gradle 中配置,它本身自己都沒配置好,因此不會監聽到。

settings.gradle 代碼:

include ":app"
複製代碼

build.gradle 代碼:

//對子模塊進行配置
subprojects { sub ->
    sub.beforeEvaluate { proj ->
        println "子項目beforeEvaluate回調..."
    }
}
println "根項目配置開始---"
task rootTest {
    println "根項目裏任務配置---"
    doLast {
        println "執行根項目任務..."
    }
}
println "根項目配置結束---"
複製代碼

app/build.gradle 代碼:

println "APP子項目配置開始---"
afterEvaluate {
    println "APP子項目afterEvaluate回調..."
}
task appTest {
    println "APP子項目裏任務配置---"
    doLast {
        println "執行子項目任務..."
    }
}
println "APP子項目配置結束---"
複製代碼

在根目錄執行:gradle -q,結果以下:

根項目配置開始---
根項目裏任務配置---
根項目配置結束---
子項目beforeEvaluate回調...
APP子項目配置開始---
APP子項目裏任務配置---
APP子項目配置結束---
APP子項目afterEvaluate回調...
複製代碼

project.android 獲取到AppExtension:

Gradle

Gradle 提供的生命週期回調方法不少,部分與 Project 裏的功能雷同:

//在project進行配置前調用,child project必須在root project中設置纔會生效,root project必須在settings.gradle中設置纔會生效
void beforeProject(Closure closure)
//在project配置後調用
afterProject(Closure closure)
//構建開始前調用
void buildStarted(Closure closure)
//構建結束後調用
void buildFinished(Closure closure)
//全部project配置完成後調用
void projectsEvaluated(Closure closure)
//當settings.gradle中引入的全部project都被建立好後調用,只在該文件設置纔會生效
void projectsLoaded(Closure closure)
//settings.gradle配置完後調用,只對settings.gradle設置生效
void settingsEvaluated(Closure closure)
複製代碼
  • beforeProject()/afterProject()
    等同於Project中的beforeEvaluateafterEvaluate
  • settingsEvaluated()
    settings腳本被執行完畢,Settings對象配置完畢
  • projectsLoaded()
    全部參與構建的項目都從settings中建立完畢
  • projectsEvaluated()
    全部參與構建的項目都已經被評估完

咱們修改 setting.gradle 的代碼以下:

gradle.settingsEvaluated {
    println "settings:執行settingsEvaluated..."
}
gradle.projectsLoaded {
    println "settings:執行projectsLoaded..."
}
gradle.projectsEvaluated {
    println "settings: 執行projectsEvaluated..."
}
gradle.beforeProject { proj ->
    println "settings:執行${proj.name} beforeProject"
}
gradle.afterProject { proj ->
    println "settings:執行${proj.name} afterProject"
}
gradle.buildStarted {
    println "構建開始..."
}
gradle.buildFinished {
    println "構建結束..."
}
include ":app"
複製代碼

這個時候的執行結果以下:

settings:執行settingsEvaluated...
settings:執行projectsLoaded...
settings:執行test beforeProject
根項目配置開始---
根項目裏任務配置---
根項目配置結束---
settings:執行test afterProject
settings:執行app beforeProject
子項目beforeEvaluate回調...
APP子項目配置開始---
APP子項目裏任務配置---
APP子項目配置結束---
settings:執行app afterProject
APP子項目afterEvaluate回調...
settings: 執行projectsEvaluated...
構建結束...
複製代碼

能夠看到 gradle.beforeProject 與 project.beforeEvaluate 是相似的,一樣 afterProject 與 afterEvaluate 也是相似的。

除此以外,Gradle 還有一個通用的設置生命週期監聽器的方法:addListener

image.png

上面的 BuildListener、ProjectEvaluationListener 等與前面的部分 API 功能是一致的,這裏再也不贅述了。

TaskExecutionGraph(Task執行圖)

Gradle 在配置完成後,會對全部的 task 生成一個有向無環圖,這裏叫作 task 執行圖,他們決定了 task 的執行順序等。一樣,Gradle 能夠對 task 的執行生命週期進行監聽。

//任務執行前掉用
void afterTask(Closure closure)
//任務執行後調用
void beforeTask(Closure closure)
//全部須要被執行的task已經task之間的依賴關係都已經確立
void whenReady(Closure closure)
複製代碼

經過 gradle.getTaskGraph() 方法來獲取 task 執行圖:

TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
    println "task whenReady"
}
taskGraph.beforeTask { Task task ->
    println "任務名稱:${task.name} beforeTask"
}
taskGraph.afterTask { Task task ->
    println "任務名稱:${task.name} afterTask"
}
複製代碼

生命週期回調的執行順序:

gradle.settingsEvaluated->
gradle.projectsLoaded->
gradle.beforeProject->
project.beforeEvaluate->
gradle.afterProject->
project.afterEvaluate->
gradle.projectsEvaluated->
gradle.taskGraph.graphPopulated->
gradle.taskGraph.whenReady->
gradle.buildFinished
複製代碼

四、自定義插件開發

三種方式

類型 說明
Build script 把插件寫在 build.gradle 文件中,通常用於簡單的邏輯,只在該 build.gradle 文件中可見
buildSrc 項目 將插件源代碼放在 rootProjectDir/buildSrc/src/main/groovy 中,只對該項目中可見,適用於邏輯較爲複雜
獨立項目 一個獨立的 Groovy 和 Java 項目,能夠把這個項目打包成 Jar 文件包,一個 Jar 文件包還能夠包含多個插件入口,將文件包發佈到託管平臺上,供其餘人使用。本文將着重介紹此類。

具體從插件開發能夠參考

須要注意的是 在main目錄下建立
一、resources/META-INF/gradle-plugins文件夾,
二、在gradle-plugins文件夾下建立一個xxx.properties文件,(com.learntransform.testtransform.properties)
注意:這個xxx就是在app下的build.gradle中引入時的名字,例如:apply plugin: ‘xxx’(apply plugin:'com.learntransform.testtransform'
三、在文件書寫引用到插件 implementation-class=me.xsfdev.learntransform.Hotfix

image.png

插件的本地化

  • 本地插件module
group = 'com.learntranform'
version = '1.0.1'
uploadArchives {
    repositories {
        flatDir {
            name "localRepository"
            dir "../app/localRepository/libs"
        }
    }
}
複製代碼
  • 工程的gradle
buildscript {
    ext.kotlin_version = '1.2.41'
    repositories {
        flatDir {
            name 'localRepository'
            dir "app/localRepository/libs"
        }
        mavenLocal()
        jcenter()
        google()
    }
    dependencies {
        classpath(group: 'com.plugintest', name: 'hellodsl', version: '1.0.0') {
            changing = true
        }
        classpath(group: 'com.learntransform', name: 'learntransform', version: '1.0.1') {
            changing = true
        }
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.novoda:bintray-release:0.8.0' //jcenter添加
        classpath 'com.xsfdev:complexcriptdsl:1.0.0'
    }
}
複製代碼

完整代碼能夠參考 LearnGradle

五、經常使用方法

android.applicationVariants

更多參考須要看源碼

android.applicationVariants 返回的是

public DomainObjectSet<ApplicationVariant> getApplicationVariants() {
        return applicationVariantList;
   }
複製代碼

一層層追 相關的有

image.png

在BaseVariant中
image.png

Task#upToDateWhen

每次都會編譯

outputs.upToDateWhen { true } doesn't mean "the task is up-to-date." It just means that the outputs are up-to-date for that particular spec. Gradle will still do its own up-to-date checks.
The other thing that may be confusing is where the task's actions are defined. If the actions are defined in the build script, the build script itself is an input to the task. So changes to the build script will make the task out-of-date.
So if I had a task like:

task myTask {
    def outputFile = file("output.txt")
    outputs.file outputFile
    doLast {
        outputFile.text = "Done"
    }
    outputs.upToDateWhen { false }
}
複製代碼

Whenever I run this, myTask is out-of-date. If I switch the false to true, the first time I run it, the task is out-of-date still (because the buildscript changed). When I run it again, it would be up-to-date (all inputs are the same). You'll see this at --info level logging.

Failed to notify project evaluation listener

The versions of the Android Gradle plugin and Gradle are not compatible.

參考

相關文章
相關標籤/搜索