Gradle进阶
Posted 58金融技术club
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle进阶相关的知识,希望对你有一定的参考价值。
Groovy
gradle和groovy的关系:gradle是一种构建工具,groovy是gradle脚本中使用的语言。
Groovy是一种动态语言。这种语言比较有特点,它和Java一样,也运行于Java虚拟机中。简单粗暴点儿看,你可以认为Groovy扩展了Java语言。比如,Groovy对自己的定义就是:Groovy是在 java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。除了语言和Java相通外,Groovy有时候又像一种脚本语言。当我执行Groovy脚本时,Groovy会先将其编译成Java类字节码,然后通过Jvm来执行这个Java类。图1展示了Java、Groovy和Jvm之间的关系。
实际上,由于Groovy Code在真正执行的时候已经变成了Java字节码,所以JVM根本不知道自己运行的是Groovy代码。下面我们将介绍Groovy。由于此文的主要目的是Gradle,所以我们不会过多讨论Groovy中细枝末节的东西,而是把知识点集中在以后和Gradle打交道时一些常用的地方上。
我们前面看到的那些build.gradle 配置文件,和xml 等的配置文件不同,这些文件可以说就是可以执行的代码,只是他们的结构看起来通俗易懂,和配置文件没什么两样,这也是Google 之所以选择Groovy 的原因。除此之外,Groovy 是一门JVM 语言,也就是,Groovy 的代码最终也会被编译成JVM 字节码,交给虚拟机去执行,我们也可以直接反编译这些字节码文件。
Groovy基础语法
变量
在groovy 中,没有固定的类型,变量可以通过def
关键字引用,比如:
def name = 'Andy'
我们通过单引号引用一串字符串的时候这个字符串只是单纯的字符串,但是如果使用双引号引用,在字符串里面还支持插值操作,
def name = 'Andy'def greeting = "Hello, $name!"
方法
类似 python 一样,通过def
关键字定义一个方法。方法如果不指定返回值,默认返回最后一行代码的值。
def square(def num) {
num * num
}
square 4
类
Groovy 也是通过Groovy 定义一个类:
class MyGroovyClass { String greeting String getGreeting() { return 'Hello!'
}
}
在Groovy 中,默认所有的类和方法都是
pulic
的,所有类的字段都是private
的;和java一样,我们通过
new
关键字得到类的实例,使用def
接受对象的引用:def instance = new MyGroovyClass()
而且在类中声明的字段都默认会生成对应的setter,getter方法。所以上面的代码我们可以直接调用
instance.setGreeting 'Hello, Groovy!'
,注意,groovy 的方法调用是可以没有括号的,而且也不需要分号结尾。除此之外,我们甚至也可以直接调用;我们可以直接通过
instance.greeting
这样的方式拿到字段值,但其实这也会通过其get方法,而且不是直接拿到这个值。
map、collections
在 Groovy 中,定义一个列表是这样的:
List list = [1, 2, 3, 4, 5]
遍历一个列表是这样的:
list.each() { element ->println element}
定义一个 map 是这样的:
Map pizzaPrices = [margherita:10, pepperoni:12]
获取一个map 值是这样的:
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']
闭包
在Groovy 中有一个闭包的概念。闭包可以理解为就是 Java 中的匿名内部类。闭包支持类似lamda
形式的语法调用。如下:
def square = { num -> num * num}
square 8
如果只有一个参数,我们甚至可以省略这个参数,默认使用it
作为参数,最后代码是这样的:
Closure square = { it * it
}
square 16
理解闭包的语法后,我们会发现,其实在我们之前的配置文件里,android
,dependencies
这些后面紧跟的代码块,都是一个闭包而已。Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号。
闭包和方法的区别
首先,必须使用def关键字定义函数,函数也不能嵌套使用;
其次,函数不能访问在外部通过def定义的变量:
例:函数:
def c=10;
def f(){
println c;//编译通过
}
//f();//运行时报错
闭包:
闭包可以访问外部的变量
def x=1;
def b={
x*2;//闭包可以访问外部的变量
}
函数可以有返回值:
1.如果有显示地使用return关键字,则返回return指定的返回值,其后面的语句不再执行;
2.如果没有显式地使用return关键字,则返回函数最后一行语句的运行结果;
3.如果使用void关键字代替def关键字定义函数,则函数的返回值将为null。
注意:如果一个函数与一个闭包拥有相同的名称和参数,则调用的时候将执行函数,而不是闭包。
Groovy in Gradle
了解完 groovy 的基本语法后,我们来看看 gradle 里面的代码就好理解多了。
apply
apply plugin: 'com.android.application'
这段代码其实就是调用了
project
对象的apply
方法,传入了一个以plugin
为key的map。完整写出来就是这样的project.apply([plugin: 'com.android.application'])
实际调用的时候会传入一个
DependencyHandler
的闭包,代码如下:
Gradle工作流程
Gradle的工作流程其实蛮简单,用一个图26来表达:
图26告诉我们,Gradle工作包含三个阶段:
初始化阶段。对我们前面的multi-project build而言,就是执行settings.gradle
配置阶段:Initiliazation phase的下一个阶段是Configration阶段。
Configration阶段的目标是解析每个project中的build.gradle。比如multi-project build例子中,解析每个子目录中的build.gradle。在这两个阶段之间,我们可以加一些定制化的Hook。这当然是通过API来添加的。
Configuration阶段完了后,整个build的project以及内部的Task关系就确定了。前面说过,一个Project包含很多Task,每个Task之间有依赖关系。Configuration会建立一个有向图来描述Task之间的依赖关系。所以,我们可以添加一个HOOK,即当Task关系图建立好后,执行一些操作。执行阶段。当然,任务执行完后,我们还可以加Hook。
以下是如何hook各个阶段:
settings.gradle可以在配置阶段修改
before可以在配置阶段前添加自己的代码
在task执行过程中可以通过taskGraph whenReady函数来hook对应的task。
在任务执行完时可以通过buildFinished方法来在任务执行后做收尾工作。
每个task也有自己的生命周期,也分为配置阶段和执行阶段,其中doFirst和doLast都已经属于执行阶段了。
task Task1 {
println “hello” // 这段代码是在gradle配置阶段执行的,这部分是闭包的形式
}
task Task2{
def name =“hello”// 这段代码是在gradle配置阶段执行的
doLast{
println name // 这段代码是在gradle执行阶段执行的
}
}
task Task3 << {// << 语法糖,相当于doLast,因此整个代码都是在执行阶段执行的
println name // 这段代码是在gradle执行阶段执行的
}
我们尝试在build.gradle中添加如下控制:
我们执行assembleDebug可以查看到执行顺序。
taskgraph.whenReady方法会在任务树生成后,先执行,graphpopulated方法在其后执行,beforeTask和TaskExecutionListener中的beforeExecute都是在任务执行前执行,afterTask和afterExecute都是在task之后执行。
我们一般都可以通过当前执行的task.name来获取当前task的名字,来获取对应的task。
task获得完成后可以通过task.doFirst {闭包}或者task.doLast {闭包}的方式在task执行阶段的doFirst和doLast方法处添加自己的逻辑处理。
来看下源码定义:
如果刚才我们在beforeTask中添加如下代码:
那么我们在assembleDebug这个task中就会看到如下输出了:
综上:我们讲解了gradle的生命周期,以及如何干预gradle的编译过程来实现一些我们自己的操作,比如热修复ClassLoader方式的插桩就可以在任务结束后添加对应的插桩操作。这一部分在后续的脚本操作中会比较重要。
以上是关于Gradle进阶的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin基础从入门到进阶系列讲解(基础教程篇)详解build.gradle文件
我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情