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)进行创建和配置


如果大家喜欢,请不吝打赏!!

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 139,905评论 20 593
  • 这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在...
    呆萌狗和求疵喵阅读 12,686评论 28 74
  • Gradle是基于Groovy的动态DSL,而Groovy是基于JVM的,Groovy的语法和Java很类似。 C...
    HoooChan阅读 3,460评论 0 5
  • Gradle对于很多开发者来说有一种既熟悉又陌生的感觉,他是离我们那么近,以至于我每天做项目都需要他,但是他又是离...
    阿_希爸阅读 7,796评论 12 197
  • 今年的元宵节除了少了吵闹的鞭炮声外,与往年无异,耳边倒是清净了不少,照例蜷缩在自己的床头,划拉着手机,看央视...
    懂我笑容任阅读 45评论 0 1