全面理解Gradle

一、简介

Gradle是一个基于JVM的构建工具,是一款通用灵活的构建工具,支持maven, Ivy仓库,支持传递性依赖管理,而不需要远程仓库或者是pom.xml和ivy.xml配置文件,基于Groovy,build脚本使用Groovy编写。
Gradle不单单是一个配置脚本,它的背后是三门语言。
1)、Groovy Language
Groovy 也是一门语言,Groovy是一门jvm语言,它最终是要编译成class文件然后在jvm上执行,所以Java语言的特性Groovy都支持,我们完全可以混写Java和Groovy。
那Groovy的优势是什么呢?Groovy提供了更加灵活简单的语法,大量的语法糖以及闭包特性可以让你用更少的代码来实现和Java同样的功能。比如解析xml文件,Groovy就非常方便,只需要几行代码就能搞定,而如果用Java则需要几十行代码。
2)、Gradle DSL
DSL的全称是Domain Specific Language,即领域特定语言,或者直接翻译成“特定领域的语言”,算了,再直接点,其实就是这个语言不通用,只能用于特定的某个领域,俗称“小语言”。因此DSL也是语言。
如Android项目中Projrct的build.gradle中buildscript和allprojects都是DSL,DSL是基于Groovy实现,可以很方便通过代码控制这些DSL来达到你的构建目的。
3)、Android DSL
DSL的定义如上。module下build.gradle中的android中的就是Android DSL。针对Android平台特有的。Gradle也可以用来构建后台应用。

如何学习Gradle?

  • 学习 Groovy(http://docs.groovy-lang.org/)
  • 学习 Gradle DSL(https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html)
  • 学习 Android DSL和Task(http://google.github.io/android-gradle-dsl/current/index.html)

二、Gradle相关文件

1、Setting文件

在Gradle中,定义了一个设置文件,用于初始化以及工程树的配置。设置文件的默认名字是setting.gradle,放在根工程目录下。主要作用是配置子工程。Gradle中多
工程是通过工程树表示。根工程相当于Android Studio中的Project,子工程相当于Module,一个根工程有很多个子工程。一个子工程只有在setting文件中配置了Gradle才会识别,才会在构建的时候包含进去。setting.gradle文件配置如下:

include ':app', ':libgroovytest'

2、Build文件

每个Project都会有一个Build文件,该文件是该Project构建的入口。可以在这里对该Project进行配置,如配置版本、需要哪些插件、依赖哪些库。

三、Gradle脚本的执行时序

Gradle脚本的执行分为三个过程:

1、初始化

分析有哪些module将要被构建,为每个module创建对应的 project实例。这个时候settings.gradle文件会被解析。settings.gradle代码如下:

include ':app', ':a', ':b'

Gradle 将会为它们三个分别创建一个 Project 对象实例。

2、配置

处理所有的模块的 build.gradle,处理依赖,属性等。这个时候每个模块的build.gradle文件会被解析并配置,这个时候会构建整个task的链表(这里的链表仅仅指存在依赖关系的task的集合,不是数据结构的链表)。(例如 assembleDebug task 的执行依赖于其他 tasks 先执行,而这个依赖关系的确定就是在 Configuration 阶段)。配置阶段task闭包中的代码会执行,doFirst和doLast闭包中的代码不会执行。
配置完以后有一个重要的回调project.afterEvaluate,表示build.gradle中所有的模块都已经配置完了,可以准备执行task了。在app的build.gradle中添加如下代码:

project.afterEvaluate {
    println "app project.afterEvaluate>>"
}

打印日志如下:

> Configure project :app
app build.gradle start
app project.afterEvaluate>>

当app下的build.gradle配置阶段执行完后会调用project.afterEvaluate。

3、执行

根据task链表来执行某一个特定的task,这个task所依赖的其他task都将会被提前执行。首先执行task中 doFirst {} 闭包中的内容,最后执行 doLast {} 闭包中的内容
下面有一个工程,lib1是一个库,app引用了这个库。项目的工程目录如下:
在这里插入图片描述执行clean任务日志如下:

-------------------------
settings.gradle start
settings.gradle end

> Configure project :
-------------------------
Project build.gradle start
project gradle testTask
Project build.gradle stop

> Configure project :app
-------------------------
app build.gradle start
app build.gradle finish
app project.afterEvaluate>>

> Configure project :lib1
-------------------------
lib1 build.gradle start
lib1 gradle testTask
lib1 gradle testTask11
lib1 build.gradle finish
lib1 evaluate start
lib1 evaluate end
:clean
:app:clean
:lib1:clean

可以看到,Gradle执行的时候遵循如下顺序:

1. 首先解析settings.gradle来获取模块信息,这是初始化阶段;
2. 然后配置每个模块,配置的时候并不会执行task(不执行doFirst和doLast),会执行task闭包中的内容;
3. 配置完了以后,有一个重要的回调project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行task了;
4. 执行指定的task(执行doFirst和doLast)。

四、Task

1、Project和Task的联系

一个Project就是在你的业务范围内,被你抽象出来的一个个独立的模块,你可以根据项目情况抽象归类,最后一个个Project组成了你的整个Gradle构建
一个Project包含很多个Task,即每个Project是由多个Task组成的。Task就是一个操作,一个原子性的操作,比如打个jar包、复制一份文件、编译一次Java代码,这就是一个Task。

2、doFirst和doLast

doFirst和doLast中的代码,不执行这个任务时,是不会执行的,但是直接写在闭包中的,就也是在这两个函数外的代码,是在配置阶段就会执行的。运行任务时,doFirst中的代码最先执行,doLast中的代码最后执行。
切记大部分的内容是写在 doLast{} 或 doFirst{} 闭包中,因为写在如果写在 task 闭包中的话,会在 Configuration 阶段也被执行。当执行其他任务时会执行所有任务task闭包中的代码。当执行自己的任务,自己任务 doLast{} 和 doFirst{} 闭包中的代码才会执行
在app的build.gradle添加如下代码:

task testExecTask {
    println '我会在 Configuration 阶段执行'
    doFirst {
        println '我仅会在 testExecTask 的 Execution 阶段执行'
    }
    doLast {
        println '我仅会在 testExecTask 的 Execution 阶段执行'
    }
}

写在 task 闭包中的内容是会在 Configuration 中就执行的,例如上面的 “ println ‘我会在 Configuration 阶段执行’”;而 doFirst {}、doLast {} 闭包中的内容是在 Execution 阶段才会执行到(doFirst {}、 doLast {} 实际上就是给当前 task 添加 Listener,这些 Listeners 只会在当前 task Execution 阶段才会执行)。
在AS中执行名字gradlew testExecTask运行testExecTask任务打印的日志如下:

> Configure project :app
testExecTask>>我会在 Configuration 阶段执行
> Task :app:testExecTask
doFirst>>我仅会在 testExecTask 的 Execution 阶段执行
doLast>>我仅会在 testExecTask 的 Execution 阶段执行

doLast也可以使用<<简便代替,<<后跟一个闭包。具体使用方式如下:

task testLeftShiftTask << {
    println 'testLeftShiftTask>>doLast'
}
testLeftShiftTask.doFirst{
    println 'testLeftShiftTask>>doFirst'
}

运行打印日志如下:

> Task :app:testLeftShiftTask
testLeftShiftTask>>doFirst
testLeftShiftTask>>doLast

五、创建Gradle插件

1、脚本插件

脚本插件就是一个普通的gradle构建脚本,通过在一个foo.gradle脚本中定义一系列的task,另一个构建脚本build.gradle通过apply from:'foo.gradle’即可引用这个脚本插件。
先新建文件customtask.gradle。在文件中定义一个任务如下:

project.task("customtask") {
    doLast {
        println("$project.name:customtask")
    }
}

然后在需要引用该插件的Moudle的build.gradle中引用customtask.gradle。引用代码如下:

apply from: 'customtask.gradle' // 引用脚本插件

然后就可以通过命令运行这个任务了。

gradlew customtask

运行这个任务的日志如下:

> Task :app:customtask
app:customtask

2、对象插件

对象插件是指实现了org.gradle.api.Plugin接口的类。Plugin接口需要实现void apply(T target)这个方法。该方法中的泛型指的是此Plugin可以应用到的对象,而我们通常是将其应用到Project对象上。
新建一个Java Library Moudle,更改目录/main/java为/main/groovy。
在这里插入图片描述
在build.gradle中引用groovy插件,代码如下:

apply plugin: 'groovy'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation localGroovy()
}

新建CustomPluginInBuildSrc类,代码如下:

package com.demo.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

class CustomPluginInBuildSrc implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.task('showCustomPluginInBuildSrc') {
            doFirst {
                println('task in CustomPluginInBuildSrc')
            }
        }
    }
}

由于buildSrc目录是gradle默认的目录之一,该目录下的代码会在构建是自动编译打包,并被添加到buildScript中的classpath下,所以不需要任何额外的配置,就可以直接被其他模块的构建脚本所引用。
在app的build.gradle中引用该插件,代码如下:

apply plugin: com.demo.plugin.CustomPluginInBuildSrc

然后通过gradlew showCustomPluginInBuildSrc运行插件中的任务,日志如下:

> Task :app:showCustomPluginInBuildSrc
task in CustomPluginInBuildSrc

3、Extension

先举个例子感受下Extension的作用。在上面代码基础上。在CustomPluginInBuildSrc中增加如下代码:

class CustomPluginInBuildSrc implements Plugin<Project> {
    @Override
    void apply(Project project) {
        def extension = project.extensions.create('customPluginExtension', CustomPluginExtension)
        project.task('CustomPluginExtension') {
            doLast {
                println extension.message
            }
        }
    }
}

通过project.extensions.create创建extensioncreate方法第一个参数是我们在build.gradle中使用时的名字,第二个参数是对应的Extension类名。然后创建任务CustomPluginExtension,在任务中打印的CustomPluginExtension的成员变量message。
CustomPluginExtension类的代码如下,其实就是定义一个JavaBean:

class CustomPluginExtension {
    String message = 'Hello World'
}

然后在build.gradle中就可以使用customPluginExtension,通过闭包指定message 的值。代码如下:

customPluginExtension{
    message 'hello gradle'
}

然后我们执行命令gradlew CustomPluginExtension,打印日志如下:

> Task :app:CustomPluginExtension
hello gradle

注意这里打印的“hello gradle”,就是我们在build.gradle闭包中指定的字符串。
这样有什么用处?我们可以在build.gradle中定义闭包设置一些参数,这些参数会传到我们定义的插件中,即在插件中我们可以获取build.gradle中定义的参数
我们先来看一段 Android 应用的 Gradle 配置代码:

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.hm.iou.thinapk.demo"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

相信做 Android 应用开发的同学,对这段代码都快看吐了吧。记得当初刚从 eclipse 转到 Android Studio 的时候,看这些配置就像看天书一样,只知道按规定配置就可以了。但是为什么要这样配置?除此外还支持哪些配置?为什么一定要在 android 这个命名空间下配置呢?可以不可以定义自己的特殊配置呢?
上面这个 android 打包配置,就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。
在 app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.android.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.android.library’,先来看一张截图:
在这里插入图片描述注意查看android gradle插件源码的方法是添加android gradle的依赖,添加依赖代码如下:

implementation 'com.android.tools.build:gradle:3.0.0'

图中类 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);
    }
    
    @NonNull
    @Override
    protected BaseExtension createExtension(
            @NonNull Project project,
            @NonNull ProjectOptions projectOptions,
            @NonNull Instantiator instantiator,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull SdkHandler sdkHandler,
            @NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
            @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
            @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
            @NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
            @NonNull ExtraModelInfo extraModelInfo) {
        return project.getExtensions()
                .create(
                        "android",
                        AppExtension.class,
                        project,
                        projectOptions,
                        instantiator,
                        androidBuilder,
                        sdkHandler,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        buildOutputs,
                        extraModelInfo);
    }
	...
}

在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。以后当我们不知道 android 里有哪些配置时,除了查看 API 文档以外,还可以直接翻看 BaseExtension 源码。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值