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)
- 通过官方网站下载压缩包并解压 http://www.groovy-lang.org/download.html
- 配置环境变量
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()
}
- 定义一个map集合
- 输出map集合
- 输出map集合的key所组成的集合
- 输出所有value值得集合
- 输出集合中key1所对应的值
- 输出集合中key1所对应的值
- 对key4进行赋值并加入map集合当中
- 遍历输出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的知识写笔记记录一下,如果有什么错误的地方还请大神多多指正。
网友评论