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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

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