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.

参考

相关文章
相关标签/搜索