对Gradle的浅显理解

刚开始学安卓一直觉得Gradle这个东西很神秘,也很麻烦。以至于写了很久的安卓之后对Gradle还是一点都不懂,只知道可以方便的引入第三方库,至于其他行的配置,就更只是眼熟。光能写代码却不懂编译用的工具,这好像不太合适,索性买了本书专门研究了下。看完做下笔记,记录对它的理解。

一、Gradle

1、Groovy

Gradle基于groovy语言,这是一个兼容Java的扩展语言。语法非常灵活。环境可以在虚拟机中安装linux进行搭建,便于练习。

2、任务(Task)

Gradle中最基本的一个单元就是task了,常用定义方式如下:

1
2
3
task test {
println 'hello world'
}

而熟悉Java的话,其实看上去挺别扭的,它还可以这么写:

1
2
3
task('test',{
println 'hello world'
})

这就比较符合Java了,方法名加括号里写参数。

3、闭包(Closure)

首先来说,上面的写法,如果看过Objective C的话就不会很奇怪。它把一个闭包,也就是一个代码块当做参数进行传递。方法原型是task(String name,Closure Closure)

而Groovy语法中,闭包如果是最后一个参数,可以提到括号外,也就是上面的写法等同于:

1
2
3
task('test') {
println 'hello world'
}

接着,括号里只有一个参数,那么括号又可以省略,就变成了最开始写的那一种了。

定义task除了这种,还可以使用Gradle提供的TaskContainer对象tasks的create方法:

1
2
3
tasks.create('test',{
println 'hello world'
})

4、关于<<

最开始看书和一些帖子的时候,会经常看到大家定义task时,总是有两种格式:

1
2
3
task test {println 'hello world'}
//或者
task test << {println 'hello world'}

单独拿出来运行时,一点区别都没有,所以比较纳闷,也没人提这个问题。

后来想了下,<<其实应该是移位符,但移位符为什么写在这里,这种方式写出来有什么作用。

还是回到Groovy语法上,了解到每个task中有一个doLast方法,控制执行顺序,常见写法:

1
2
3
4
5
task test {
doLast{
println 'hello world'
}
}

语法中,这个doLast操作等价于leftShift,替换方法后也可以这么写:

1
2
3
4
task test {}
test.leftShift{
println 'hello world'
}

最后,左移位可以简写为<<并放到任务名后,就成为了常见的写法。

到这里真是不得不感叹这个语言的灵活性,很有意思。

5、Project对象

一个gradle文件中,有一个代表自身的Project对象,调用方法时一般都是省略的,比如:

1
2
3
task test << {closure}
//等价于
project.tasks.create('test').doLast{closure}

每个task都是project的一个属性,所以task之间可以做流程交互和控制。

可以在Project中以ext自定义属性,比局部变量有更广泛的作用域,在其他位置用$做参数调用。

二、Android Gradle

Android Studio中使用的,是官方基于Gradle写成的插件,我们在文件中第一行看到的:

1
apply plugin: 'com.android.application'

就是对App插件的应用,其他还有com.android.libray可以配置一个库工程。

了解了语法之后就理解到每一行的配置,本质上都是一个方法,传递一个闭包,或者是一个属性,使用set方法进行了设定。所以如果哪一行不懂或是没用过,点进去看源码就好,都是开源的。那么就没有必要一个一个去写出来总结了,提几个感觉很有用的特性吧。

1、defaultConfig中一些属性的定义方式

比如版本号、版本名,如果想要比较清晰的结构,可以分模块来写,便于修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建一个version.gradle文件,专门存放版本
ext {
appVersionCode = 1
appVersionName = "1.0.0"
}
//然后再build.gradle中引用他
apply from: 'version.gradle'
android {
...
defaultConfig{
...
versionCode appVersionCode
versionName appVersionName
}
}

版本号和版本名除了自己写,也可以通过git中的tag信息和修改次数来动态获取。也就需要我们在方法中执行命令行,gradle也为我们提供了很方便的方法——exec,比如:

1
2
3
4
5
6
7
8
9
10
11
/**
* 以git中tag数量作为版本号
*/
def getAppVersionCode(){
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git','tag','--list'
standardOutput = stdout
}
return stdout.toString().split("\n").size()
}

2、签名信息的隐藏

签名信息有时为了方便会直接放到项目中,但如果不想让其他人看到,又保证每个人可以打签名包,可以将信息配置到服务器上,通过引用环境变量的方式来获取,例如:

1
2
3
4
5
6
7
8
9
10
11
android {
...
signingConfigs {
def appStoreFile = System.getenv("STORE_FILE")
//其他配置省略,还有storePassword、keyAlias、keyPassword
release{
storeFile file(appStoreFile)
...
}
}
}

3、占位符

当多渠道打包(这个其实还能用一篇的篇幅来说,不过暂时感觉没有必要细说)时,我们的AndroidManifest文件中,需要定义不同的内容,但类似这样的需求,又不能写多个文件来增加维护的复杂度,gradle为我们提供了十分方便的manifestPlaceholder占位符,可以用来替换内容。比如:

1
2
3
4
5
6
7
8
9
10
11
12
android {
...
//这里写一个简单的多渠道分包配置
productFlavors{
google {
manifestPlaceholder.put{"Channel","google"}
}
baidu {
manifestPlaceholder.put{"Channel","baidu"}
}
}
}

在清单文件中,就可以这样配置:

1
<meta-data android:name="Channel" android:value="${Channel}" />

当我们打好不同的包,反编译后就可以看到不同包的manifest文件中这里显示了不同渠道名。

如果渠道很多,要写的重复代码还是很多,可以通过遍历函数来写:

1
2
3
4
5
6
7
8
9
10
11
android {
...
productFlavors{
google {}
baidu {}
}
productFlavors.all {
flavor->
manifestPlaceholders.put{"Channel",name}
}
}

这个遍历用途还有很多,比如不同渠道的apk文件名,也都可以方便的进行配置。

三、结束

还有很多可以说的,比如自定义属性、各种配置、多渠道构建、代码覆盖率、未用资源清理、ndk支持等等。不过我不是写书,没必要去全面总结,只是对一些有自己理解的地方进行记录,加深印象。剩下的,就是根据自己的需求看文档,会使用就够了。

系统的看了这些之后,也算是对Gradle有了一定的理解,原来都想不到,为了更好的进行编译、打包、测试等,这个文件都能写到几百行。所以想要精通一个门类,还是需要各方面都去学习,磨刀不误砍柴工说的没错,只有我们更好的利用工具,才能把工作做得更好更有效率。

Powered by Hexo

Copyright © 2018 - 2022 Yshen's Blog All Rights Reserved.

UV : | PV :

Fork me on GitHub