Android Gradle构建-理解DSL语言以及运行机制

前言

这篇文章可能跟Android的关系不是很深,主要介绍Groovy是如何一步步解析Android的DSL语言,这样你在配置一些Gradle文件的时候可以更加得心应手。阅读本文之前你需要具有一点Android基础,并且需要了解一些Groovy语言的基本特性,例如Closure[], def等含义。Groovy是一种运行在JVM虚拟机上的脚本语言,能够与Java语言无缝结合,如果想了解Groovy可以查看IBM-DeveloperWorks-精通Groovy

DSL的好处

我们打开Android的build.gradle文件,会看到类似下面的一些语法:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

这是一个简单的build.gradle配置文件,我们可以看到buildscript里有配置了repositoriesdependencies,而repositoriesdependencies里面又可以配置各自的一些属性。可以看出通过这种形式的配置,我们可以层次分明的看出整个项目构建的一些定制,又由于Android也遵循约定大于配置的设计思想,因此我们仅仅只需修改需要自定义的部分即可轻松个性化构建流程。

Gradle下的Groovy脚本-build.gradle

在Groovy下,我们可以像Python这类脚本语言一样写个脚本文件直接执行而无需像Java那样既要写好Class又要定义main()函数,因为Groovy本身就是一门脚本语言,而Gradle是基于Groovy语言的构建工具,自然也可以轻松通过脚本来执行构建整个项目。作为一个基于Gradle的项目工程,项目结构中的settings.gradlebuild.gradle这类xxx.gradle可以理解成是Gradle构建该工程的执行脚本,当我们在键盘上敲出gradle clean aDebug这类命令的时候,Gradle就会去寻找这类文件并按照规则先后读取这些gradle文件并使用Groovy去解析执行。

让DSL"活起来"-Groovy的魔法

要理解build.gradle文件中的这些DSL是如何被解析执行的,需要介绍Groovy的一些语法特点以及一些高级特性,官方有一篇关于DSL特性的描述,如果你追求原味直接看这个即可。 Domain-Specific-Languages
下面将介绍一下比较重要的几个特点。

Command chains - 链式命令

Groovy的脚本具有链式命令(Command chains)的特性,根据这个特性,当你在Groovy脚本中写出a b c d的时候,Groovy会翻译成a(b).c(d)执行,也就是将b作为a函数的形参调用,然后将d作为形参再次调用返回的实例(Instance)中的c方法。其中当做形参的bd可以作为一个闭包(Closure)传递过去。
下面是一些简单的实例:

// equivalent to: turn(left).then(right)
turn left then right

// equivalent to: take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours

// equivalent to: paint(wall).with(red, green).and(yellow)
paint wall with red, green and yellow

// with named parameters too
// equivalent to: check(that: margarita).tastes(good)
check that: margarita tastes good

// with closures as parameters
// equivalent to: given({}).when({}).then({})
given { } when { } then { }

Groovy也支持某个方法传入空参数,但需要为该空参数的方法加上圆括号。

// equivalent to: select(all).unique().from(names)
select all unique() from names

如果链式命令(Command chains)的参数是奇数,则最后一个参数会被当成属性值(Property)访问。

// equivalent to: take(3).cookies
// and also this: take(3).getCookies()
take 3 cookies

Operator overloading - 操作符重载

有了Groovy的操作符重载(Operator overloading),==会被Groovy转换成equals方法,这样你就可以放心大胆地使用==来比较两个字符串是否相等了,在我们编写gradle脚本的时候也可以尽情使用。关于Groovy的所有操作符重载(Operator overloading)可以查阅Operator overloading

DelegatesTo - 委托

委托(DelegatesTo)可以说是Gradle选择Groovy作为DSL执行平台的一个重要因素了。通过委托(DelegatesTo)可以很简单的定制一个控制结构体(Custom control structures),比如你可以写如下这段代码:

email {
    from 'dsl-guru@mycompany.com'
    to 'john.doe@waitaminute.com'
    subject 'The pope has resigned!'
    body {
        p 'Really, the pope has resigned!'
    }
}

上面这段代码便是我们自己定义的DSL语言了,当然这也是一段脚本,我们可以结合上文讲到的Groovy的链式命令(Command chains)来手动解析一下这段脚本含义,下面拆分下这些步骤吧:

  1. 首先email {...}这段被执行,其执行方式等效于email({...}), Groovy调用email方法,然后将{...}这个闭包(Closure)作为参数传递进去;
  2. from 'dsl-guru@mycompany.com'等效于from('dsl-guru@mycompany.com')解析执行;
  3. subject 'The pope has resigned!'等效于subject('The pope has resigned!')解析执行;
  4. body {...}同步骤1一样,{...}这个闭包作为body方法的参数,等效于body({...})解释执行;
  5. p 'Really, the pope has resigned!'等效于p('Really, the pope has resigned!')解释执行。
    当然,有个问题我们需要清楚,当我们调用email {...}这种方法的时候,{...}闭包中的方法比如from 'dsl-guru@mycompany.com'等不是Groovy Shell自动去调用执行的,而是通过Groovy的委托(DelegatesTo)方式来完成,这块后文会讲到。

接下来我们可以看下解析上述DSL语言的代码:

def email(Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

我们先定义了一个email(Closure)的方法,当执行上述步骤1的时候就会进入该方法内执行,EmailSpec是一个继承了参数中cl闭包里所有方法比如fromto等等的一个类(Class),通过rehydrate方法将cl拷贝成一份新的实例(Instance)并赋值给codecode实例(Instance)通过rehydrate方法中设置delegateownerthisObject的三个属性将clemail两者关联起来被赋予了一种委托关系,这种委托关系可以这样理解:cl闭包中的fromto等方法会调用到email委托类实例(Instance)中的方法,并可以访问到email中的实例变量(Field)。DELEGATE_ONLY表示闭包(Closure)方法调用只会委托给它的委托者(The delegate of closure),最后使用code()开始执行闭包中的方法。
当然,Groovy提供了很多灵活的委托(DelegatesTo)方式,这块可以通过阅读官方文档了解。

Android DSL解读

下面我们直接开始解读上文提供的build.gradle这个文件,让我们来看看Groovy是如何让这些DSL发挥了作用。
build.gradle:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

可以看到这份build.gradle依次执行了buildscript({...})all projects{...}all projects{...}task...方法。通过Android Studio点击某个方法我们可以发现buildscriptallprojectstask都指向了Project类,由此可以看出Project类是整个build.gradle脚本文件的委托类,其中必然有一个Project的实例(Instance)在管理这些类,当我们执行诸如biuldscriptallprojectstask这些方法的时候,就能够对这个Project实例进行配置。由此最后Gradle基于Project类的实例(Instance)进行整个项目的构建流程。
接下来描述下这份grade脚本文件的执行步骤,为了描述方便,我将buildscript方法中的闭包(Closure)称为C1,然后其他闭包(Closure)对应关系依次为repositories->C2dependencies->C3all projects->C4repositories->C5,最后一个task...这一部分闭包(Closure)就不定义了,至于原因,你可以猜下~接下来按照步骤来说吧:

  1. 执行buildscript方法,并把C1作为形参传递进去,进行构建脚本的一些配置,此时C1的委托者(The delegate of closure)是Project类中的ScriptHandler的实例(Instance);
  2. 执行C1中的方法,此时执行repositories方法并以C2作为形参,配置仓库地址,C2的委托者(The delegate of closure)是RepositoryHandler类的实例(Instance),负责相关仓库的配置;
  3. 执行C2中的方法,由于C2的委托者(The delegate of closure)是RepositoryHandler的实例(Instance),因此执行了RepositoryHandlerjcenter方法,将它配置成我们项目的远程仓库;
  4. 执行dependencies方法并将C3作为形参,配置一些相关的构建依赖,C3的委托者(The delegate of closure)是DependencyHandler类的实例(Instance);
  5. 执行C3中的方法,同步骤3一样,调用委托者(The delegate of closure)DependencyHandler的方法classpath并把相关依赖作为形参传递过去,不过这里你会发现用IDE进去却是对应add(String configurationName, Object dependencyNotation)这个方法,这里一定有玄机,感兴趣的朋友可以自个探索下;
  6. 同上面原理一样,执行all projectsC4repositoriesC5等这类方法,配置了所有项目工程的仓库为jcenter,这里不再赘述;
  7. 接下来是task clean ...这部分DSL了,这块的逻辑存在一个比较奇怪的问题,根据Groovy的链式命令(Command chains),此处执行的顺序应该是clean([type: Delete], {delete rootProject.buildDir}) -> task(...),然而实际上并非如此,其实际执行应该是task([type: Delete], 'clean', {delte rootProject.buildDir})(此处仅个人理解,感谢@花京院典明 指正,之后有时间把这块 DSL 解析过程完善下),由此完成一个Task的创建,由于指定了typeDelete,所以{delete rootProject.buildDir}这个闭包(Closure)的委托者(The delegate of closure)就是Delete类的实例(Instance),具体实现方式可以参考Gradle的源码。

结语

至此,你应该对于Android DSL有了一个大概的了解吧。由于本人水平有限,如果其中有错误之处还望指出,233333~

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 导语: 随着技术的发展,不管是前端开发、服务端开发或者是移动端开发(移动也是前端的一个分支)中都会用到自动化构建工...
    伊始雨深阅读 3,020评论 0 4
  • Gradle是基于Groovy的动态DSL,而Groovy是基于JVM的,Groovy的语法和Java很类似。 C...
    HoooChan阅读 7,446评论 0 7
  • Gradle对于很多开发者来说有一种既熟悉又陌生的感觉,他是离我们那么近,以至于我每天做项目都需要他,但是他又是离...
    阿_希爸阅读 9,569评论 10 199
  • Android Studio作为Android应用开发的官方IDE,默认使用Gradle作为构建工具,所以对于An...
    feil0n9wan9阅读 1,655评论 1 6