GRADLE脚本的语法和BUILD流程
导语
Android Studio中使用了Gradle进行build。我阅读了groovy官方文档,Gradle官方文档及一些源代码,Android插件的官方文档及一些源代码,希望给大家介绍一下Gradle脚本的语法和处理流程。简单Groovy是一种运行在JVM上的语言, Gradle是使用Groovy实现的, Android插件提供了android特定的功能。
1. Gradle脚本的build流程
1.1Groovy和脚本
Groovy会把脚本编译成groovy.lang.Script的子类。groovy.lang.Script是一个抽象类,它有一个抽象方法run(). 如果有一个脚本的文件名是Main,它的内容是:
println 'Hello from Groovy'
它编译后生成的类是:
class Main extends groovy.lang.Script {
def run() {
println 'Hello from Groovy'
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
脚本中的语句会成为run方法的实现。
Gradle脚本编译生成的类当然也继承自groovy.lang.Script,并同时实现了Gradle自己的script接口org.gradle.api.Script。
1.2 Gradle脚本对象的代理对象
每一个Gradle脚本对象都有一个代理对象。Settings脚本的代理对象是Setting对象,Build脚本的代理对象是Project对象。每一个在脚本对象未找到的属性和方法都会转到代理对象。
Groovy和Java的一个不同之处就是Groovy可以动态的添加方法和属性。动态添加的方式之一是覆盖propertyMissing和methodMissing方法,Gradle就是采用这种方式实现了脚本对象的代理对象。下面是Gradle脚本对象的基类BasicScript的实现代码片段:
public abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
......
private Object target;
private DynamicObject dynamicTarget;
......
public Object propertyMissing(String property) {
if ("out".equals(property)) {
return System.out;
} else {
return dynamicTarget.getProperty(property);
}
}
......
public Object methodMissing(String name, Object params) {
return dynamicTarget.invokeMethod(name, (Object[])params);
}
}
1.3 Gradle脚本的build流程
Gradle脚本的build流程分为3个阶段:
(1)初始化阶段
Gradle支持单个和多个工程的编译。在初始化阶段,Gradle判断需要参与编译的工程,为每个工程创建一个Project对象,并建立工程之间的层次关系。这个阶段执行Settings脚本。
(2)配置阶段
Gradle对上一步创建的Project对象进行配置。这个阶段执行Build脚本
(3)执行阶段
Gradle执行选中的task。
1.4一个demo
下面是demo工程的文件层次,这个demo会被后面的部分使用。这个例子包含一个app子工程和一个library子工程。settings.gradle是Setttings脚本,三个build.gradle都是Build脚本。
--settings.gradle
--build.gradle
--app
--build.gradle
--mylibrary
--build.gradle
2. Settings脚本
2.1 Settings脚本的内容
Settings脚本通常比较简单,用于初始化project树。demo中settings.gradle的内容是:
include ':app', ':mylibrary'
2.2 groovy的相关语法
groovy允许省略语句结尾的分号,并允许在方法调用时省略括号,上面的代码等价于:
include(':app', ':mylibrary');
2.3 Gradle的相关语法
初始化脚本的Script对象会有一个Project代理对象。在Script对象没有定义的属性和方法调用就会被转到Project对象。上面的语句实际上调用的是Project对象的include方法,该方法的原型如下:
void include(String[] projectPaths)
这个方法将给定的工程添加到build中。工程路径的格式是: 以一个可选的”:”的开始,它表示”:”前面有一个不需要名字的根工程;剩下的部分是以”:”分隔的工程名。例如, “:app”中”:”的是可选的,它表示”:”前面有一个不需要名字的根工程。
运行”gradle projects”可以获得这个demo的project树:
Root project 'AndroidGradleDemo'
+--- Project ':app'
\--- Project ':mylibrary'
3. Build脚本
3.1 app工程的Build脚本
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
......
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
minifyEnabled false
//getDefaultProguardFile() will return the full path of 'proguard-android.txt'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
.......
3.2 Groovy相关的语法
1)Map
Groovy可以直接使用Java中的各种集合类型,例如Map和List等等。在初始化语法上有一些扩展
def key = 'name'
//创建一个Map。注意"Guillaume"的key是字符串"key"而不是变量key的值
def person = [key: 'Guillaume']
assert !person.containsKey('name')
assert person.containsKey('key')
//创建一个Map。我们使用()包围了key。这种情况下,"Guillaume"的key是变量key的值"name"
def person2 = [(key): 'Guillaume']
assert person2.containsKey('name')
assert !person2.containsKey('key')
(2)闭包
(2.1)Syntax
Groovy中的闭包是一个匿名的代码块。它的语法规则是:
{ [closureParameters -> ] statements }
)[closureParameters→]是可选的以”,”分隔的参数列表
*)statements是0或多条Groovy语句
下面是闭包的一些例子:
{ item++ }
//A closure using an implicit parameter (it)
{ println it }
//In that case it is often better to use an explicit name for the parameter
{ it -> println it }
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
{ reader ->
def line = reader.readLine()
line.trim()
}
(2.2)Owner, delegate and thisGroovy中的闭包有三个重要的概念: Owner, delegate and this
*)this是指定义闭包的类
*)Owner是指直接包含闭包的类或闭包
*)delegate是指用于解析闭包中属性和方法调用的第三方对象
下面的代码段说明了this和闭包的区别:
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() }
//calling the closure will return the instance of Enclosing where the the closure is defined
assert whatIsOwnerMethod() == this
def whatIsOwner = { owner }
assert whatIsOwner() == this
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner }
cl()
}
//then owner corresponds to the enclosing closure, hence a different object from this!
assert nestedClosures() == nestedClosures
}
}
下面的代码演示了delegate的作用。
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }
cl.delegate = p
//在设置了delegate后,闭包中的name属性被解析成作为delegate的Person的属性
assert cl() == 'IGOR'
Gradle中大量使用闭包的delegate来实现对各种对象的配置。
(3)转化成Java语法后的代码
Map<String, Object> map = new HashMap<String, Object>();
map.put("plugin", "com.android.appliaction");
apply(map);//这个方法会代理给Project对象
android({
//这个闭包的delegate是Android插件添加的extension
compileSdkVersion(23);
......
buildTypes({
//这个闭包的delegate是 NamedDomainObjectContainerConfigureDelegate.
debug({
applicationIdSuffix(".debug")
});
release({
minifyEnabled(false);
//getDefaultProguardFile() will return the full path of 'proguard- android.txt'
proguardFiles(getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro');
});
});
});
3.3 Gradle的相关语法
(1)Project查找方法的规则
Build脚本的代理对象是Project对象。Project对象从6个范围中查找方法:
*)Project对象本身定义的方法
*)脚本文件中定义的方法
*)被插件添加的extension. extension的名字可以做为方法名
*)被插件添加的convension方法。
*)工程中的task。task的名字可以作为方法名
*)父工程中的方法。
(2)apply plugin: ‘com.android.application’
“apply plugin: ‘com.android.application’”等价于调用Project对象的apply方法,该方法的原型是:
void apply(Map<String,?> options)
Project.apply方法用于应用脚本和插件。我们指定了键值对”plugin”:”com.android.application”, 因此这个调用会添加Android插件。Android插件会添加一个名字是”android”,类型是com.android.build.gradle.AppExtension的extension.
(3)android{…}
这个代码块等价于”android({…});”, 即调用了一个方法名是”android”的方法,该方法的参数是一个闭包。当调用Project对象的android方法时,实际上是找到了名字是”android”的extension, 把这个extension设为闭包的代理并运行这个闭包。从而闭包中的方法调用实际上都是对com.android.build.gradle.AppExtension的调用。这就是上面提到的Project查找方法的规则中的一条:被插件添加的extension的名字可以做为方法名。
*)查找extension的逻辑是在ExtensionsStoage.configureExtension中,代码如下
public <T> T configureExtension(String methodName, Object ... arguments) {
Closure closure = (Closure) arguments[0];
ClosureBackedAction<T> action = new ClosureBackedAction<T>(closure);
//根据名字查找extension
ExtensionHolder<T> extensionHolder = extensions.get(methodName);
return extensionHolder.configure(action);//Line 69
}
*)设置闭包的delegate并运行闭包的逻辑是在ClosureBackedAction.execute中, 代码如下:
public void execute(T delegate) {
if (closure == null) {
return;
}
try {
if (configureableAware && delegate instanceof Configurable) {
((Configurable) delegate).configure(closure);
} else {
Closure copy = (Closure) closure.clone();
copy.setResolveStrategy(resolveStrategy);
//设置delegate
copy.setDelegate(delegate);
if (copy.getMaximumNumberOfParameters() == 0) {
copy.call();
} else {
//运行闭包
copy.call(delegate);//
}
}
} catch (groovy.lang.MissingMethodException e) {
if (Objects.equal(e.getType(), closure.getClass()) && Objects.equal(e.getMethod(), "doCall")) {
throw new InvalidActionClosureException(closure, delegate);
}
throw e;
}
}
(4)buildTypes{…}
buildTypes{…}位于android{…}代码块中,它等价于AppExtension的buildTypes方法,该方法的参数是一个闭包。AppExtension中定义了一个buildTypes方法,代码如下:
void buildTypes(Action<? super NamedDomainObjectContainer<DefaultBuildType>> action) {
plugin.checkTasksAlreadyCreated();
action.execute(buildTypes)
}
buildTypes的闭包的delegate是一个NamedDomainObjectContainerConfigureDelegate类型的实例,因此该闭包内部的方法都会被delegate到这个对象,诸如”debug”方法,”release”方法,或者其他的以用户自定义的build type作为名字的方法。相应的代码是在NamedDomainObjectContainerConfigureDelegate的基类ConfigureDelegate的invokeMethod中,代码如下:
public Object invokeMethod(String name, Object paramsObj) {
......
//对已经创建过的build type进行配置
_delegate.invokeMethod(name, result, params);
if (result.isFound()) {
return result.getResult();
}
MissingMethodException failure = null;
if (!isAlreadyConfiguring) {
// Try to configure element
try {
//创建新的build type并进行配置
_configure(name, params, result);
} catch (MissingMethodException e) {
// Workaround for backwards compatibility. Previously, this case would unintentionally cause the method to be invoked on the owner
// continue below
failure = e;
}
if (result.isFound()) {
return result.getResult();
}
}
}
*)对于已经创建过的build type,调用_delegate.invokeMethod(),进而调用DefaultNamedDomainObjectCollection$ContainerElementsDynamicObject.invokeMethod()进行配置
*)对于需要创建的build type,调用_configure(), 进而调用AbstractNamedDomainObjectContainer.create(String name, Closure configureClosure)进行创建和配置
如果大家喜欢,请不吝打赏!!