暂时收藏学习gradle程序员

Learning Gradle

2017-03-18  本文已影响298人  feil0n9wan9

Android Studio作为Android应用开发的官方IDE,默认使用Gradle作为构建工具,所以对于Android应用开发来说,Gradle是必须要掌握的工具。然而现实是,很多Android应用开发人员都不太了解Gradle,并且网上大部分关于Android Gradle的资料都是帮助解决某个具体的配置问题,缺乏系统深入的讲解。本文就来系统且深入的学习Gradle

Java构建工具的发展

Java构建工具最早出现的是Ant。Ant里的每一个任务(target)都可以互相依赖。Ant的最大缺点就是依赖的外部库也要添加到版本控制系统中,因为Ant没有一个机制来把这些外部库文件放在一个中央库里面,结果就是不断的拷贝和粘贴代码。

随后Maven在2004年出现了,Maven引入了标准的项目和路径结构,还有依赖管理,不幸的是自定义的逻辑很难实现,唯一的方法就是引入插件。

随后Ant通过Apache Ivy引入依赖管理来跟上Maven的脚步,Ant和Ivy集成实现了声明式的依赖,比如项目的编译和打包过程。

Gradle的出现满足了很多现在构建工具的需求,Gradle提供了一个DSL(领域特定语言),一个约定优于配置的方法,还有更强大的依赖管理,Gradle使得我们可以抛弃XML的繁琐配置,引入动态语言Groovy来定义你的构建逻辑。

Why Gradle

Android Studio Project Site上对Android Studio为何选用Gradle作为构建工具描述如下:

Gradle is an advanced build system as well as an advanced build toolkit allowing to create custom build logic through plugins. Here are some of its features that made us choose Gradle:

DSL,领域特定语言,指不像通用目的语言那样目标范围涵盖一切软件问题,而是专门针对某一特定问题的计算机语言,如init.rc,renderscript等。

理解Groovy

由于Gradle是基于Groovy开发的,要深入理解Gradle,必须先了解Groovy。Groovy概括的说就是把写Java程序变得像写脚本一样简单,写完就可以执行,Groovy内部会将其编译成Java字节码,然后启动虚拟机来执行。Groovy是用于Java虚拟机,具有像PythonRubySmalltalk语言特性的敏捷的动态语言,使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。Groovy使用方式基本与Java代码的使用方式相同,其设计时充分考虑了Java集成,这使Groovy与Java代码的互操作很容易。

Groovy安装

Mac上可以直接通过Homebrew安装Groovy,具体命令如下:

$ brew install groovy

安装完成后可通过如下命令查看:

$ groovy -v
Groovy Version: 2.4.8 JVM: 1.8.0_45 Vendor: Oracle Corporation OS: Mac OS X

Groovy基础语法

def var = "Hello Groovy"
println var
println var.class

var = 5
println var
println var.class

输出结果如下:

Hello Groovy
class java.lang.String
5
class java.lang.Integer
def name = 'Jerry'
println 'His name is $name'
println "His name is $name"

def members = """
    'Terry'
    "Larry" """
println "Team member is: " + members

输出结果如下:

His name is $name
His name is Jerry
Team member is:
    'Terry'
    "Larry"
def getValue(name) {// def is must
    name + "'s value is 10"
}
value = getValue "Terry"
println value

结果如下:

Terry's value is 10
class Person {
    int id
    String name

    void setId(id) {
        println "setId($id)"
        this.id = id
    }

    String toString() {
        "id=$id, name=$name"
    }
}
merry = new Person()
merry.id = 1    // call merry.setId(1)
merry.setName "Merry"
println merry
println merry.getId().class
jerry = new Person(id: 2, name: "Jerry")
println jerry

运行结果如下:

setId(1)
id=1, name=Merry
class java.lang.Integer
setId(2)
id=2, name=Jerry

下面的例子展示通过metaClass向String对象中动态添加属性和方法:

def msg = "Hello Groovy"
msg.metaClass.upper = { delegate.toUpperCase() }
msg.metaClass.lower = msg.toLowerCase()
println msg.upper()
println msg.lower

运行结果如下:

HELLO GROOVY
hello groovy

Groovy动态性

Groovy动态性示例如下:

class Dog {
    def bark() {
       println 'woof!'
    }
    def sit() {
       println 'sitting!'
    }
    def jump() {
       println 'boing!'
    }
}

def dog = new Dog()
def acts = ['sit','jump','bark']
acts.each {
    dog."${it}"()
}

运行结果如下:

sitting!
boing!
woof!

另一个动态性的例子:

class LightOn
{
    def doing()
    {
       println 'Ligth turning on...'
    }
}

class LightOff
{
    def doing()
    {
       println 'Ligth turning off...'
    }
}

class Switch
{
    def control(action)
    {
       action."doing"()
    }
}
def sh = new Switch()
sh.control(new LightOn())

运行结果如下:

Ligth turning on...

Groovy List

tmp = ["Jerry", 19 , true]
println "tmp[1] = " + tmp[1]
println tmp[5] == null
tmp[10] = 3.14
println "The size is " + tmp.size
println tmp

运行结果如下:

tmp[1] = 19
true
The size is 11
[Jerry, 19, true, null, null, null, null, null, null, null, 3.14]

Groovy Map

def score = "mark"
// id and name are treated as String
// "$score" is also correct
tmp = [id: 1, name: "Jerry", (score): 92]
println tmp
println "id: " + tmp.id
println "name: " + tmp["name"]
tmp.height = 183
println "height: " + tmp.height

运行结果如下:

[id:1, name:Jerry, mark:92]
id: 1
name: Jerry
height: 183

Groovy Range

tmp = 1..5
println tmp
println tmp.from
println tmp.to
tmpWithoutEnd = 1..<5
println tmpWithoutEnd.step(2)
println ""
println ""
println tmpWithoutEnd


tmp = 1.0f..5.0f
println tmp

运行结果如下:

[1, 2, 3, 4, 5]
1
5
[1, 3]


[1, 2, 3, 4]
[1.0, 2.0, 3.0, 4.0, 5.0]

Groovy文档

T   getFrom()
The lower value in the range.
T   getTo()
The upper value in the range.

Groovy闭包

def person = { id, name ->
    println "id=$id, name=$name"
}
person(1, "Jerry")
person.call(2, "Larry")
person 3, "Merry"

Closure resume = { "resume" }
println resume()

def hello = "Hello"
def length = hello.&length
println "The length is " + length()

def greeting = { "$hello, $it" }
// equals: greeting = { it -> "Hello, $it" }
println greeting('Groovy')

def exit = { -> "exit" }
// exit(1) <= wrong!!

def members = ["Jerry", "Larry", "Merry"]
members.each {
    println "Hello $it"
}

def welcome(name) {
    return {
        println "Welcome $name"
    }
}
println welcome("Terry")

运行结果如下:

id=1, name=Jerry
id=2, name=Larry
id=3, name=Merry
resume
The length is 5
Hello, Groovy
Hello Jerry
Hello Larry
Hello Merry
closure$_welcome_closure6@73a1e9a9

如何确定闭包的参数?查看API文档。

Groovy DSL

// 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 { }

一个例子:

def name(name) {
    println "name: $name"
    return this
}
def age(age) {
    println "age: $age"
    return this
}
def action(String tips, Closure c) {
    println "begin to $tips"
    c()
    println "end"
    return this
}

name "Jerry" age 18
// name("Jerry").age(18)

name "Herry"
age 22
action("eat") {
    println "eating..."
}

运行结果如下:

name: Jerry
age: 18
name: Herry
age: 22
begin to eat
eating...
end

Groovy脚本

Groovy脚本是什么?我们通过一个例子来看一下。

// variables.groovy

def x = 1 // or int x = 1

def printx() {
    println x
}

printx() // failed

运行结果如下:

Caught: groovy.lang.MissingPropertyException: No such property: x for class: variables
groovy.lang.MissingPropertyException: No such property: x for class: variables
    at variables.printx(variables.groovy:5)
    at variables.run(variables.groovy:8)

为何会出现找不到属性x?我们来看看反编译groovy运行时生成的.class文件。使用如下命令在-d指定的目录生成.class文件,然后使用JD-GUI查看:

groovyc –d classes variables.groovy
variables.class

可以看到:

所以变量x是在是在run函数中定义的局部变量,当然无法在printx()函数的访问。那要如何才能实现printx()函数访问变量x呢?有两种方式可以实现。
第一种方式:

x = 1 // replace def x = 1 or int x = 1

def printx() {
    println x
}

printx()

对应的.class文件反编译代码:


另一种方式:
import groovy.transform.Field;

@Field x = 1 // <= def x = 1 or int x = 1

def printx() {
    println x
}

printx()

对应的.class文件反编译代码:

Gradle介绍

Gradle被认为是Java世界构建工具的一次飞跃,它提供:

更多Gradle特性概述,可以访问Gradle概述

Why Groovy

Gradle官方网站上对为何选用Groovy来开发的原因描述如下:

We think the advantages of an internal DSL (based on a dynamic language) over XML are tremendous when used in build scripts. There are a couple of dynamic languages out there. Why Groovy? The answer lies in the context Gradle is operating in. Although Gradle is a general purpose build tool at its core, its main focus are Java projects. In such projects the team members will be very familiar with Java. We think a build should be as transparent as possible to all team members.

In that case, you might argue why we don't just use Java as the language for build scripts. We think this is a valid question. It would have the highest transparency for your team and the lowest learning curve, but because of the limitations of Java, such a build language would not be as nice, expressive and powerful as it could be. [1] Languages like Python, Groovy or Ruby do a much better job here. We have chosen Groovy as it offers by far the greatest transparency for Java people. Its base syntax is the same as Java's as well as its type system, its package structure and other things. Groovy provides much more on top of that, but with the common foundation of Java.

For Java developers with Python or Ruby knowledge or the desire to learn them, the above arguments don't apply. The Gradle design is well-suited for creating another build script engine in JRuby or Jython. It just doesn't have the highest priority for us at the moment. We happily support any community effort to create additional build script engines.

Gradle基本概念

Gradle脚本是配置脚本,脚本执行时会配置特定的对象,这些对象被称为脚本的delegate对象。脚本中可以使用delegate对象的属性和方法。所有的Gradle脚本实现了org.gradle.api.Script接口,所以脚本中可以直接使用Script接口定义的属性和方法,Script接口中找不到的属性或方法将被转交给delegate对象。同时,Gradle脚本也是Groovy脚本,同样可以包含Groovy脚本允许的元素,方法、类定义等。

gradle-delegates

Gradle生命周期

Gradle执行构建时有它自己的生命周期,概括来说可以分为3个阶段:
第一是初始化阶段。初始化阶段Gradle会创建一个Gradle对象、Settings对象和Root Project对象,然后在项目根目录寻找并执行settings.gradle文件来配置Settings对象,并生成项目的Project树。

第二是配置阶段。配置阶段Gradle会执行特定的构建脚本,默认是各Project目录下名为build.gradle的文件,以配置对应的Project。配置阶段结束时,Gradle内部会生成整个项目的有向无循环Task图。

第三是执行阶段。执行阶段Gradle依据命令行传入的task名字在Task图中找出此task的所有依赖链,并从各依赖链起点开始,沿着依赖链依次执行Task,最终得到编译产物。

gradle-build-phasesM

Gradle用户手册中关于Gradle生命周期的描述如下:


gradle-lifecycle

Gradle生命周期的例子如下:

// settings.gradle
println 'This is executed during the initialization phase.'

// build.gradle in root directory
println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}
task test {
    doLast {
        println 'This is executed during the execution phase.'
    }
}
task testBoth {
    doFirst {
      println 'This is executed first during the execution phase.'
    }
    doLast {
      println 'This is executed last during the execution phase.'
    }
    println 'This is executed during the configuration phase as well.'
}

执行结果如下:

This is executed during the initialization phase.
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.

Gradle生命周期Hook

Gradle提供了丰富的Hook机制以获知Gradle生命周期各个阶段各类事件的发生。常用的Hook可以通过Settings对象,ProjectTask对象来设置,设置方式包括配置闭包方式和设置监听器方式。

gradle-lifecycle-phases-hook

以项目评估结束事件为例说明:

// build.gradle in root directory
afterEvaluate {
    println "Project $name has been evaluated."
}

gradle.afterProject {
    println "Project $it.name is evaluated."
}

gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    void afterEvaluate(Project project, ProjectState state) {
        println "Project $project.name was evaluated."
    }
    void beforeEvaluate(Project project) {
    }
})

执行结果如下:

Project tutorial was evaluated.
Project tutorial is evaluated.
Project tutorial has been evaluated.

Gradle核心组件

Gradle的核心组件包括GradleSettingsProjectTaskDependencyPlugin等,每个核心组件都对应很多有用的属性、方法和脚本块。

Gradle组件

Gradle组件代表了一次Gradle构建,通过Gradle组件能够获取(或设置)本次构建的一些全局信息,包括gradleVersiontaskGraph,所有用到的plugins及添加各种Hook等。

Settings组件

Settings实例与settings.gradle文件存在一一对应关系,Gradle执行时会使用settings.gradle来配置对应的Settings实例。通常,在settings.gradle脚本中会通过include(String[])方法来添加对多项目的支持。Root Project会在Settings对象被创建时自动添加。另外,Settings组件还支持动态属性,除了对象接口本身的属性,还包含如下可用的属性:

示例如下:

// gradle.properties in project root directory
componentized=true

// settings.gradle
if (componentized) {
    include ':components'
}

执行结果如下:

Project tutorial was evaluated.
Project tutorial is evaluated.
Project tutorial has been evaluated.
Project components was evaluated.
Project components is evaluated.

Project组件

Project组件是编译脚本与Gradle交互的主API接口,通过Project接口能访问Gradle所有功能。Gradle运行时使用build.gradle配置对应的project对象。一个Project本质上一些列Task的集合,每一个Task执行一定的任务(类编译,生成javadoc等)。Project配置通常包括仓库,依赖,产物,分组配置,插件的管理。所有Project将都将被加入到Project层次树中,Project全路径是其在层次树中的唯一标识。Project配置还可以引入插件,插件可以使得Project配置更加模块化,并可重用。Project组件还支持5大范围的动态属性和5大范围的动态方法。

5大范围动态属性


project-5-property-scopes

5大范围动态方法


project-5-method-scopes

Ext属性示例:

// build.gradle in root directory
ext {
    gradleVersion = "3.4"
    isSnapshot = true
}
ext.javaVersion = "1.7"

task addProperty(dependsOn: clean) {
    project.ext.prop3 = true
}
if (isSnapshot) {
    println "GradleVersion: $gradleVersion"
    println "JavaVersion: $javaVersion"
    println "Prop3: ${prop3}"
}

// build.gradle in components directory
println "GradleVersion in components: $gradleVersion"
println "JavaVersion in components: ${rootProject.ext.javaVersion}"

执行结果如下:

GradleVersion: 3.4
JavaVersion: 1.7
Prop3: true
GradleVersion in components: 3.4
JavaVersion in components: 1.7

依赖管理

Gradle的依赖管理主要包括2大块:

Gradle有6大类型依赖,如下图,其中比较常用的包括External module dependencyProject dependencyFile dependency

dependency-types

依赖管理示例:

// In this section you declare where to find the dependencies of your project
repositories {
    jcenter()
    jcenter {
        url "http://jcenter.bintray.com/"
    }
    mavenCentral()
    maven {
        url "http://repo.company.com/maven2"
    }
    ivy {
        url "../local-repo"
    }
}

dependencies {
    compile 'com.google.guava:guava:20.0'
    compile project(':components')
    compile fileTree(dir: 'libs', include: '*.jar')
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

Task组件

Gradle task represents some atomic piece of work which a build performs。Task可以使用task关键字定义,也可以使用TaskContainer.create()来定义。一个Task可以依赖于另一个Task,Gradle支持使用must run aftershould run after直接控制2个Task的执行顺序。预定义的Task可以被替换。
另外Task还支持4大范围动态属性和通过Convention对象支持动态方法。

4大范围动态属性


task-4-property-scopes

Task使用示例:

// build.gradle in root project
task hello1 {
    doLast {
        println "Hello1 Task"
    }
}
task hello2 << {
    ext.prop2 = "prop2"
    println "Hello2 Task"
}
hello2.dependsOn hello1
task(hello3, dependsOn: hello2) {
    doFirst {
        println "Prop2: ${hello2.ext.prop2}"
        println "Hello3 Task"
    }
}
tasks.create('hello4').dependsOn('hello3')
hello4.doLast {
    println "Hello4 Task"
}

运行结果如下:

:hello1
Hello1 Task
:hello2
Hello2 Task
:hello3
Prop2: prop2
Hello3 Task
:hello4
Hello4 Task

Plugin组件

Gradle插件就是将很多可在多个不同项目或构建中重用的编译逻辑(属性、方法和Task等)打包起来。可以使用不同的语言来实现插件,如Groovy,Java,Scala,插件最终被编译成字节码。插件源码可位于3类地方:

Gradle Wrapper目录结构

Gradle Wrapper可以使得每个项目拥有各自的Gradle版本而不互相影响。当前项目中Gradle相关文件说明如下:

gradle-wrapper-in-project

用户主目录下Gradle相关文件说明如下:


gradle-wrapper-in-home

Android Plugin for Gradle

Android Plugin for Gradle就是通过Gradle插件机制方式实现Gradle在Android平台的扩展,详细信息请参考Gradle Plugin User GuideAndroid Gradle DSL Reference


参考

上一篇下一篇

猜你喜欢

热点阅读