tags: [android,gradle,CI/CD]
categories: gradle
top: false
toc: true
cover: false
img: /featureImg/gradle.png
summary: 开始自定义task的旅程
—-
什么是task
task是gradle中一个至关重要的概念,gradle的构建过程就是由一个个task组成。task之间有依赖关系,会形成一个有向无环图(Direceted Acyclic Graph,DAG),构建过程中每个task只会执行一次,不会出现循环的情况
gradle构建的三个阶段
初始化阶段
gradle根据setting.gradle来组织多项目构建,如果当前目录没有setting.gradle会去上级目录寻找,如果找不到会将当前目录作为一个单一的项目进行构建
配置阶段
在这个阶段gradle会确定task的依赖关系,形成执行task需要的DAG
执行阶段
这个阶段是执行task的阶段,task执行会遵循配置阶段确定的依赖关系
但是没有依赖关系的task的执行顺序是不确定的,这样可以充分利用多核cpu加速构建过程
生命周期

gradle提供了几个生命周期方法来hook
Gradle对象中的生命周期方法
//setting.gradle文件中println "[hook] - setting.gradle配置中..."gradle.settingsEvaluated {println "[hook] - settingsEvaluated"}gradle.projectsLoaded {//此时可以访问project对象println "[hook] - projectsLoaded"}gradle.buildStarted {println "[hook] - buildStarted"}//第二阶段//========== 每个project配置开始和结束的回调 ==================gradle.beforeProject {println "[hook] - beforeProject ${it.name}"}gradle.afterProject {println "[hook] - afterProject ${it.name}"}//========== 每个project配置开始和结束的回调 和上面应该是等价的==================gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {@Overridevoid beforeEvaluate(Project project) {println "[hook] - ${project.name} 配置阶段开始前"}@Overridevoid afterEvaluate(Project project, ProjectState state) {println "[hook] - ${project.name} 配置阶段完成后"}})//========== 所有project的配置完成 ==================gradle.projectsEvaluated {println "[hook] - projectsEvaluated"}//========== 所有DAG构建完成 ==============gradle.taskGraph.whenReady { graph ->println "[hook] - 所有DAG已确认,执行阶段开始前"}gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {@Overridevoid graphPopulated(TaskExecutionGraph taskExecutionGraph) {println "[hook] - 所有DAG已确认,执行阶段开始前"}})//============= 第三阶段 ===================//============= task执行前后的监听 ==========gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {@Overridevoid beforeExecute(Task task) {println "[hook] ======beforeExecute ${task.name} ======="}@Overridevoid afterExecute(Task task, TaskState taskState) {println "[hook] ======afterExecute ${task.name} ${taskState.didWork}======="}})gradle.buildFinished { result ->println "[hook] - 构建完成后"}
Project对象中的生命周期方法
//这个是后面经常要用到的方法//在project evaluate之后执行一些操作,例如指定task的依赖关系或者创建新的task等project.afterEvaluate {println "[hook] - afterEvaluate"}//在project evaluate之前执行一些操作,注意这个方法的调用时间//在当前build.gradle中写这个没有作用,因为配置当前build.gradle时beforeEvaluate时机已经过了//需要在rootProject的build.gradle或者setting.gradle中获取需要监听的project对象添加beforeEvaluateproject.beforeEvaluate {println "[hook] - beforeEvaluate"}
一个简单的例子
关于task的理论知识简单介绍了下,现在开始自定义一个task
在build.gradle中加入以下代码,我们就完成了一个自定义task,当然它没啥作用,只是打印hello world
task helloWorld {println "hello world"}
直接点下AndroidStudio中的GradleSync按钮,就可以在输出中看到hello world了
当然这里其实并不是在执行阶段打印的,而是在在配置阶段就已经打印了,这个和单独的 println "hello world" 没什么区别
我们要真正在执行阶段来执行我们自定义的逻辑还需要稍作修改,为task添加action
task helloWorld {doLast{println "hello world"}}//或者使用doFirsttask helloWorld {doFirst{println "hello world"}}
每个task可以有多个action,使用一个list维护,doFirst会在list头部添加一个action,doLast会在list尾部添加一个action
这样在终端中执行 ./gradlew helloWorld 会看到在执行阶段打印出了hello world
ps:在gradle 5.0之后 `Task.leftShift(Closure) 被废弃了
//这种用法不行了task helloWorld << {println "hello world"}
为什么可以这样定义一个task
在gradle中 task 是一个关键字,可以用来定义一个task
Each task belongs to a
Project. You can use the various methods onTaskContainerto create and lookup task instances. For example,TaskContainer.create(java.lang.String)) creates an empty task with the given name. You can also use thetaskkeyword in your build file
task myTasktask myTask { configure closure }task myTask(type: SomeType)task myTask(type: SomeType) { configure closure }
上面的用法和groovy的语法稍有不同
相当于下面的方法调用
task("myTask")task("myTask") { configure closure }task(type: SomeType, "myTask")task(type: SomeType, "myTask") { configure closure }
task的简单复用
在build.gradle中直接定义task是最简单的方式,这样定义的task是依赖于project的,如果一个工程有多个project,每个project都需要相同的自定task怎么办呢
一种方案是在root project中为所有子project添加task
allprojects {task helloAll {doLast {println "hello"}}}
一种方案是在一个gradle文件中定义task,在需要使用的地方apply这个文件
//在hello.gradle中定义helloWorld taskapply from: file("hello.gradle")
Gradle Plugin
直接在gradle文件中定义task只适合一些简单的脚本,复杂一些的构建逻辑如果跟项目的业务逻辑放在一起混乱且不好管理,这时我们就需要自己写一个项目专门实现这些构建逻辑了,也就是gradle插件 apply plugin: 'com.android.application' apply plugin: 'com.android.library' 这种就是google为我们提供的构建android app和库的 gradle 插件
实现 gradle 插件的方式有两种:
- buildSrc
- 单独的gradle插件工程
两者的目录结构其实是一样的
区别是buildSrc在项目中可以直接被其他子项目使用
而单独的gradle插件工程需要发布到maven仓库,添加到classPath才能使用其中的gradle插件
目录结构
一个典型的gradle 插件工程的目录结构如下
|-GradleProjectName|-src| |-main| |-java| |-groovy| |-kotlin| |-resources| |-META-INF| |-gradle-plugins| |-${pluginId}.properties|-build.gradle
build.gradle
//编译buildSrc需要用到 groovy 插件apply plugin: 'groovy'//依赖dependencies {//gradleApi() localGroovy()都是gradle的内部依赖implementation gradleApi()implementation localGroovy()//依赖其他库,如Gsonimplementation 'com.google.code.gson:gson:2.8.5'}//中央仓库repositories {google()jcenter()}
源码
java/groovy/kotlin是三种可以用来实现gradle plugin的语言,对应的源代码放到对应的文件夹,一般来说只需要groovy一个文件夹放置源码
插件命名
默认情况下插件名和项目名称是一样的,要修改插件名可以通过.properties文件实现
.properties文件的文件名表示pluginId,使用 apply plugin:pluginId 使用该插件
# 指定class路径implementation-class=me.sunhapper.gradleplugin.GradlePlugin
定义第一个插件
import org.gradle.api.Pluginimport org.gradle.api.Projectclass GradlePlugin implements Plugin<Project> {@Overridevoid apply(Project project) {//配置阶段打印出projectNameprintln(project.name)}}
自定义Task
之前出现的task都是在.gradle文件中使用task关键字直接创建的,现在我们需要用另一种方式实现Task来完成Task功能的复用
实现一个自定义Task
这里使用@TaskAction标记一个方法,这个方法会在
class GreetingTask extends DefaultTask {String greeting = 'hello from GreetingTask'@TaskActiondef greet() {println greeting}}
添加到project中
对于已经加入classpath中的gradle库或者buildSrc中定义的类,在build.gradle文件中可以直接使用
import 包名.GreetingTask// Use the default greetingtask hello(type: GreetingTask)// Customize the greetingtask greeting(type: GreetingTask) {greeting = 'greetings from GreetingTask'}
或者通过Plugin作为桥梁,来为project创建一个task
class GradlePlugin implements Plugin<Project> {@Overridevoid apply(Project target) {target.task("greeting", type: GreetingTask)}}
使用kotlin-dsl
因为groovy是一种动态语言,带来了出色的灵活性的同时也带来了代码难以理解以及错误定位比较困难的问题,同时ide的支持也无法像静态语言一样那么完善
而kotlin作为一门静态语言,而且作为JetBrain的亲儿子,在IDE的支持上比groovy好了不少,而损失的动态特性也要求gradle插件的作者使用更工程化的方式提供功能
所以使用kotlin编写gradle是一个趋势,现在gradle的官方文档中代码示例也基本有kotlin和groovy两个版本
先来看看如何将kotlin-dsl引入工程
将buildSrc中的build.gradle改为build.gradle.kts
plugins {//使用kotlin-dsl插件`kotlin-dsl`//保留groovy插件groovy}dependencies {gradleApi()localGroovy()}repositories {jcenter()}
最初我是想在build.gradle中直接加上kotlin-dsl的支持,不过因为kotlin-dsl和gradle的版本有一定得关联性,所以被坑了一把,最后虽然搞定了,但还是建议build.gradle.kts作为项目的配置文件
apply plugin: 'groovy'apply plugin: "org.gradle.kotlin.kotlin-dsl"dependencies {implementation gradleApi()implementation localGroovy()}repositories {google()jcenter()}//buildSrc是配置阶段第一个加载的,所以root project下配置的classpath对buildSrc不起作用//需要在buildSrc的build.gradle下配置buildscript {repositories {google()jcenter()maven {url "https://plugins.gradle.org/m2/"}}dependencies {//1.2.9对应gradle5.6,不同的gradle版本有对应的kotlin-dslclasspath "org.gradle.kotlin:plugins:1.2.9"// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}}
Plugin的代码没啥区别,也是实现Plugin接口
总结
- gradle构建有初始化、配置、执行三个阶段
- 可以直接在build.gradle中自定义task,也可以写在一个文件里在build.gradle中apply
- 复杂的构建逻辑可以写成一个单独的项目,buildSrc对整个项目有效
- kotlin也可以用来写gradle脚本
