Gradle基础

1. 构建文件

在Android构建项目时会自动生成三个gradle文件:setting.gradle和build.gradle以及Android模块内的build.gradle文件。

1.1 settings文件

settings文件在初始化阶段被执行,并且定义了哪些模块应该包含在构建内,Gradle会为每个settings文件创建一个Settings对象。默认情况下的内容如下:

include ':app'

在上述代码中,只有app模块被包含在内。正常来讲,单模块构建不一定需要settings文件,但是多模块构建一定需要,否则Gradle不知道哪个模块要包含在构建内。

1.2 顶层构建文件

顶层构建文件即位于根目录下的build.gradle文件。所有模块的配置参数都是在顶层的build.gradle文件中配置的。默认配置如下:

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

allprojects {
    repositories {
        google()
        jcenter()
    }
}

实际的构建配置都在buildscript代码块内。

  • repositories代码块主要用于配置项目依赖仓库,上述代码将google,jcenter两个仓库配置成仓库,配置过后我们的应用或者依赖项目就可以下载对应仓库下的一系列依赖包。

  • dependsncies代码块用于配置构建过程中的依赖包。值得注意的是不能将应用或者依赖项目所需要的依赖包包含于顶层构建文件中,默认情况下,唯一被定义的依赖包是Gradle的Android插件。每个Android模块都需要有Android插件才可以执行Android相关任务。

  • allprojects代码块可用来声明那些需要被用于所有模块的属性。

1.3 模块的构建文件

模块内部的build.gardle文件的属性只能应用在Android app模块,它可以覆盖顶层build.gradle文件的任何属性。默认情况下如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.liubohua.gradletest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
}

总体大致可分为三个部分:插件依赖、Android以及依赖包。

插件依赖

第一行用到了Android应用插件,在顶层构建文件中被配置成了依赖。

Android

引入插件之后,需要对Android进行配置,配置的基本信息如下:

  • compileSdkVersion为必须要有的属性,用来编译应用的Android API版本。

  • defaultConfig代码块用于配置应用的核心属性。此段代码中的属性可以覆盖AndroidManifest.xml中的属性。
defaultConfig {
        applicationId "com.example.liubohua.gradletest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
  • applicationId覆盖了manifest文件中得到的packagename。
  • minSdkVersion被用来配置运行应用的最小API级别,targetSdkVersion用于通知系统,该应用已经在某特定Android版本通过测试,从而操作系统不必启用任何向前兼容的行为。两种属性对应manifest的<uses-dsk>中的属性。
  • versionCode与versitionName表示为你的应用定义一个版本号和版本名称。同样会覆盖manifest当中的属性。

  • builddTypes代码块用来定义如何构建和打包不同构建类型的应用。
依赖包

依赖代码块定义了一个应用或依赖项目的所有依赖包。默认情况下会对libs文件夹下的所有jar文件构成依赖。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

2. 自定义构建

Gradle构建项目时,可以生成一个BuildGradle类,使用buildConfigField和resValue可以对这个类里面添加自定义的常量,使得在不同的构造模式下会产生不同的效果:

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String","TEST_URL","\"http://www.baidu.com\"")
            resValue("string","test_res","debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            buildConfigField("String","TEST_URL","\"http://www.google.com.hk\"")
            resValue("string","test_res","release_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

这里我们在debug模式和release模式下分别定义了test_res和TEST_URL的不通值,使用在java代码中的引用如下:

    //debug模式为http://www.baidu.com,release为http://www.google.com.hk
    BuildConfig.TEST_URL
    //debug模式为debug_res,release为release_res
    getResources().getString(R.string.test_res)

对于Project对象我们也可以添加属性,添加额外属性需要用到ext代码块。
如我们想要定义一个config.build文件来统一管理我们各个moudle的配置,config.gradle的内存如下:

ext {
    compileSdkVersion = 26
    buildToolsVersion = "26.0.2"
}

在顶层的build.gradle当中添加依赖

apply from: 'config.gradle'

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

在模块构建文件当中引用

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
}

如果不采用config.gradle当中的配置,在模块构建文件当中可以对属性重新定义,如果模块构建文件中定义的属性在顶层构建文件中已经存在,那么新属性将覆盖原来的属性。

3. 依赖配置

gradle添加依赖的方式主要有一下几种:

  • compile(implementation或api)
  • apk
  • provided
  • testCompile
  • androidTestCompile
    compile是默认的配置,在编译主应用时包含所有的依赖。该配置不仅会将依赖添加至类路径,还会生成对应的APK。

在Android studio 3.0.0中使用了最新的Gralde 4.0 里程碑版本作为gradle的编译版本。在新版本中compile 指令被标注为过时方法,而新增了两个依赖指令,一个是implement 和api,两个新指令用法相同,相比于以前的compile指令,implement指令对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。

apk配置使该依赖只会打包到APK,而不会添加到编译类路径。provided配置则完全相反,不会被打包到APK。这两个配置只适用于JAR依赖,如果在依赖项目中添加他们则会导致错误。
testCompile和androidTestCompile配置会添加用于测试的额外依赖库,在运行测试相关任务时会被使用。
除去标准配置外,Android插件还可以针对每个构建变体生成一份配置,其格式如下:buildType+配置类型,例如debugCompile、releaseProvided等。

4. 构建类型

在gradle的Android插件中,每一个模块都有一个debug构建类型,其被设置为默认构建类型。同时我们可以在buildTypes当中自定义构建类型。

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String", "TEST_URL", "\"http://www.baidu.com\"")
            resValue("string", "test_res", "debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        lbh {
            minifyEnabled true
            applicationIdSuffix ".lbh"
            versionNameSuffix "-lbh"
            buildConfigField "String", "SYAGINHG_URL", "\"http://www.lbh.com\""
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

我们新定义了lbh构建类型,并为其添加设置(applicationId添加“.lbh”后缀,为versitionName版本名添加-lbh后缀)。这样在同一个设备当中就可以同事安装lbh版本和debug两个版本,从而避免发生冲突。
我们也可以使用已经定义好的构建类型来初始化一个新的构建类型:

buildTypes {
        debug {
            minifyEnabled false
            buildConfigField("String", "TEST_URL", "\"http://www.baidu.com\"")
            resValue("string", "test_res", "debug_res")
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        lbh.initWith(buildTypes.debug)
        lbh {
            minifyEnabled true
            applicationIdSuffix ".lbh"
            versionNameSuffix "-lbh"
            buildConfigField "String", "SYAGINHG_URL", "\"http://www.lbh.com\""
        }
    }

initWith()方法复制了一个已存在的构建类型的所有属性到新的构建类型当中,当然我们可以根据需要在新的构建类型中对他们重新定义或者添加新的属性。

5. product flavor

当我们同一个APP需要产出不同版本的应用时,就需要用到product flavor。最典型的例子是一个应用的付费版和免费版。
使用productFlavor代码块中添加新的Product flavor来创建。

andrlod{
    flavorDimensions "color","price"

    productFlavors {
        red {
            dimension "color"
            applicationId 'com.example.liubohua.red'
            versionCode 3
        }
        blue {
            dimension "color"
            applicationId 'com.example.liubohua.blue'
            versionCode 4
        }
        free {
            dimension "price"
        }
        paid {
            dimension "price"
        }
    }
}

product flavor可以拥有自己的源集目录,该文件的名称格式为flavor名称+构建类型名称。
flavorDimensions字段可以为flavor添加维度,flavorDimensions后面参数的顺序将决定构建variant的名称,第一个字段将覆盖第二个字段。假设构建类型为debug和release,则上述flavor将会生成如下variant:

  • blueFreeDebug和blueFreeRelease
  • bluePaidDebug和bluePaidRelease
  • redFreeDebug和redFreeRelease
  • redPaidDebug和redPaidRelease

6. Groovy

6.1 简介

Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。

简单的配置方法如下(Mac)

vim ~/.bash_profile

在弹框中输入

export PATH=$PATH:/Users/xuhongchuan/install/groovy-2.4.13/bin

退出保存。

  • 让修改的设置生效
source ~/.bash_profile
  • 打印版本号确认是否安装成功
groovy -v

打印的结果为

Groovy Version: 2.4.13 JVM: 1.8.0_141 Vendor: Oracle Corporation OS: Mac OS X

则表示安装成功。

  • 使用IntelliJ IDEA即可进行开发。

6.2 基本语法

先来看一个简单的例子

def x = 10;
def s1 = 'hello ${x} world'
println s1
def s2 = "hello ${x} world"
println s2

结果输出

hello ${x} world
hello 10 world

6.3 定义变量和方法

在groovy中支持动态类型,即不需要指定变量的类型。变量定义可以使用关键字def。

def a = 1 //定义一个整形
def b = 'dfdsfa' //定义一个字符串
def double c = 1.1 //定义一个double类型,double可以省略

无返回类型的函数定义,必须使用def关键字 ,最后一行代码的执行结果就是本函数的返回值。

def func(){
    return 8
}
int func2(def a,def b){
    a*b
}
println func() //8
println func2(2,3) //6

定义方法时,return可以省略,如果不使用return,则默认最后一行为返回值。 返回类型可以指定为整形,也可用def,使用def返回类型与最后一行类型相同。

另一种定义方法的方式为:

def func = {num,num2 ->
    num = num+num2 //num=3
    num*num2
}
println(func(1,2)) //6

这种定义方式在Gradle当中很常见。这不是一个常规方法,而是一个closure,即匿名代码块,可以接受参数和返回值。
在这种定义中内含了未分类参数it的概念。如果你没有明确给closure指定参数,则Groovy会自动添加一个参数it,使用方式如下

def func = {
    it * it
}
println(func(8))        //64

6.4 类

在Groovy中定义一个类的方式如下:

class GroovyClass {
    def str
    def getStr() {
        return str
    }
    void setStr(str) {
        this.str = str
    }
}

定义方式与java中的定义方式类似,但是在没有修饰符修饰的情况下。Groovy中的默认访问修饰符与Java不同。类和方法一样是公有的,但是成员变量是私有的。GroovyClass的使用方式与java类似。

def testClass = new GroovyClass()
testClass.setStr('hello world')
println(testClass.str)//hello world
println(testClass.getStr())  //hello world

当你调用一个变量时,实际调用的是该变量的getter方法,所以后两种方式打印的结果完全相同。

6.5 Groovy容器

这里介绍两种Groovy容器:List和Map

  • List:其底层对应Java中的List接口。
List list = [1,2,3,4]
println list[0]
list[2] = 5
list<<6
list.each {elements->
    println(elements)
}

上述代码初始化了一个List,对list[0]单独进行输出之后对list[2]进行赋值,通过“list<< 6”语句向list集合中添加元素,之后对List进行遍历并对元素进行输出。
输出结果为

1
1
2
5
4
6

遍历时也可以使用it变量。

List list = [1,2,3,4];
list.each {
    println(it)
}
  • map:键-值表
def map = [key1: "value1", key2: "value2", key3: "value3"]
println map
println(map.keySet())
println(map.values())
println("key1的值:" + map.key1)
println("key1的值:" + map.get("key1"))
map.put("key4", "value4")

Iterator it = map.iterator()
while (it.hasNext()) {
    println "遍历map: " + it.next()
}
  1. 定义一个map集合
  2. 输出map集合
  3. 输出map集合的key所组成的集合
  4. 输出所有value值得集合
  5. 输出集合中key1所对应的值
  6. 输出集合中key1所对应的值
  7. 对key4进行赋值并加入map集合当中
  8. 遍历输出map集合
    上述代码输出如下
[key1:value1, key2:value2, key3:value3]
[key1, key2, key3]
[value1, value2, value3]
key1的值:value1
key1的值:value1
遍历map: key1=value1
遍历map: key2=value2
遍历map: key3=value3
遍历map: key4=value4

7. 任务

Gradle在构建过程中一共可以分为三个阶段:初始化阶段、配置阶段和执行阶段。当我们使用task创建完一个任务时,需要添加“<<”告知Gradle代码块会在执行阶段执行,而不是配置阶段。

task hello << {
    println('hello world')
}

输出结果为:

> Task :hello
hello world

BUILD SUCCESSFUL in 1s

实际上想要在执行阶段执行代码,需要使用doFirst()或者doLast()来为一个task添加代码。我们之前用来定义task说的左移运算符(<<)就是doFirst()得缩写。

task hello  {
    println('hello world')
    doFirst {
        println 'Hello'
    }
    doLast {
        println 'hello again'
    }
    doLast {
        println 'Goodbye'
    }
}

输出结果

> Configure project :
hello world

> Task :hello
Hello
hello again
Goodbye

值得注意得是doFirst必须总是第一个添加到task当中,后续的doLast会依次顺序执行。
还有两个方法在构建时会经常用到。mustRunAfter()以及dependsOn()

  • mustRunAfter():用于对task进行排序,当定义该方法后,如果涉及到的两个任务都被执行,无论定义顺序如何,执行顺序都会按照之前定义好的执行。
task task1 << {
    println 'test1'
}
task task2 << {
    println 'test2'
}
task2.mustRunAfter task

执行./gradlew task1 task2或者./gradlew task2 task1输出结果相同

> Task :task1
test1
> Task :task2
test2
  • dependsOn():定义一个任务必须依赖于另一个任务。
task task1 << {
    println 'test1'
}
task task2 << {
    println 'test2'
}
task2.dependsOn task1

执行./gradlew task2直接输出结果

> Task :task1
test1
> Task :task2
test2

相比于mustRunAfter,在未声明task1的时候执行task2也会出发task1。

8 尾声

刚刚接触简书,本人是17年毕业的萌新,最近学习了一点关于gardle的知识写笔记记录一下,如果有什么错误的地方还请大神多多指正。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 什么是 Gradle ? 所谓构建工具就是对你的项目进行编译、运行、签名、打包、依赖管理等一系列功能的合集,传统的...
    方子晗阅读 440评论 0 0
  • 今天需要到政务中心办事,和女儿约定好她去书店看书,我办完事情就去接她。辗转坐车、步行就花了一个多小时,来到政务中心...
    曾子玲阅读 334评论 1 1
  • 我带着你 你带着钱 我们出去买买买
    童童爱吃鱼阅读 110评论 0 0