gradle 是什么
gradle 跟 ant/maven 一样,是一种依赖管理/自动化构建工具。但是跟 ant/maven 不一样,它并没有使用 xml 语言,而是采用了 Groovy 语言,创造了一种 DSL(特定领域语言),这使得它更加简洁、灵活,更加强大的是,gradle 完全兼容 maven 和 ivy。更多详细介绍可以看它的官网。
gradle 按照 gradle 的规则(build.gradle、settings.gradle、gradle-wrapper、gradle 语法)来进行构建。
AndroidStudio 中与 Gradle 相关的主要文件
打开 AndroidStudio,新建一个工程,就可以看到如下目录结构:
Android Studio 中的 android 项目通常至少包含两个 build.gradle 文件,一个是 project 范围的,另一个是 module 范围的,由于一个 project 可以有多个 module,所以每个 module 下都会对应一个 build.gradle。project 下的 build.gradle 是基于整个 project 的配置,而 module 下的 build.gradle 是每个模块自己的配置。
grade-wrapper.properties 文件主要是告诉开发工具,如果在电脑中找不到 Gradle 工具,那么要到哪个网址去下载 Gradle 工具,下载哪个版本的。
整个工程目录下的 build.gradle 文件跟模块下的 build.gradle 文件名字相同,但是负责整个工程所有模块的构建。
settings.gradle文件最简单,就是告诉 gradle 工具工程中包含哪几个模块。
settings.gradle
打开这个文件,会看到如下内容:
1 | include ':app' |
意思是我们这个工程中目前只有一个模块,模块的名字叫做 app,书写格式是 include 关键字后边跟一对单引号,单引号中是模块名称,名称前边还加了一个冒号,比如项目有两个模块 module-a、module-b,那么就需要在这个文件中进行配置,中间用英文逗号隔开即可,格式如下:
1 | include ':module-a',':module-b' |
grade-wrapper.properties
打开这个文件可以看到下图所示内容:
1 | #Sun Jan 26 18:51:01 CST 2020 |
这个文件告诉系统如果电脑上没有 Gradle 工具,要到哪个网址下载,所以整个文件最重要的就是最后一行,其中h的反斜杠是一个转义符,上面的那些 distributionPath 之类的配置是告诉系统如果需要下载 Gradle 工具的,下载完要保存在哪里。
project#build.gradle
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. |
以 buildscript 为例,它是一个方法,可以加括号变成如下形式:
1 | buildscript ({ |
此处的大括号是一个闭包,闭包相当于可以被传递的代码块或者方法。
repositories 配置下面 dependencies 依赖的仓库地址,该依赖都是给 gradle plugin 使用的,比如 android 项目会用到下面的 plugin:
1 | apply plugin: 'com.android.application' |
上面的 plugin 有可能是从网路上获取的,需要配置,配置的地方就是 dependencies 内的 classpath ‘com.android.tools.build:gradle:XXX’,它指定了需要使用哪些 plugin,这些 plugin 要从哪些地方哪些仓库下载,则在 repositories 内配置。
仓库 repositories 声明了两次,这其实是由于它们作用不同,buildscript 中的仓库是 gradle 脚本自身需要的资源,表明 Gradle 工具本身要怎么配置,Gradle 工具本身要从哪个 maven 仓库下载,可以看到有一个是叫做 jcenter 的 maven 仓库,所谓 maven 仓库其实就是个网站,点击进入源码,可以看到 jcenter 的网址是 http://jcenter.bintray.com ,用浏览器打开可以看到里边按目录存放了很多 jar 包和其它的库文件,google 同理。
allprojects 下的仓库是项目所有模块需要的资源。是我们工程里边所有模块的通用配置,这里规定所有模块中要用到的 jar 包也都从 google 和 jcenter 仓库中获取 ,在 allprojects 标签下配置的好处是你不需要再在每个模块下的 build.gradle 中单独配置了,如果你想在每个模块下单独指定用哪个仓库,那么这个全局的 build.gradle 中也可以不写。allprojects 事实上调用了一个方法,下面两种写法是等价的:
1 | allprojects { |
module#build.gradle
1 | //声明插件,这是一个android程序,如果是 android 库,应该是 com.android.library |
apply plugin
第一行的 apply plugin 是 Gradle 工具规定的写法,意思是我们要使用什么插件来构建项目,后边跟的是插件名称,com.android.application 是 Google 通过 Gradle 的 api,使用 Groovy 语言编写的一个插件,用于构建 Android 主工程。
如果这个模块是一个 Library 的话,应该引入的插件叫做 com.android.library,如果熟悉 Gradle 的 api 的话也可以写自己的插件,github 上有许多辅助 Android 开发的插件,可以引入 AndroidStudio 中。
版本以及包名等常规配置
android 标签下就是主要关注的地方,compileSdkVersion 是我们要用 Android 哪个版本的 api。
buildToolsVersion 是构建工具的版本号,与我们在代码中调用的 api 没什么关系,是打包用的,建议用比较新的版本。
applicationId 是我们应用的唯一标识:包名。如果这个模块是 Library,那么要把这行删掉。
defaultConfig 中是一些基本配置,它会同时应用到 debug/release 版本上。
buildTypes 标签和 signingConfigs 标签
buildTypes 表明我们可以打哪些类型的 apk 包,新建工程显示的只有 release 类型(debug 类型默认的,没有显示),意思是正式发布的 apk 要怎么打包。
buildTypes 下的 debug 标签没有显示,但是在 Build Variants 下可以看到,在 release 标签和 debug 标签下,我们可以指定要用到的混淆文件在哪个目录下,签名文件(keystore)在哪个目录下,签名文件的密码之类的,如果没有配置的话就使用默认值。
如果需要修改,则要在 android 标签下调用一个 signingConfigs 标签:
1 | android { |
signingConfigs 标签其实也是 com.android.application 插件中的,在下边随意增加配置,myConfig 这个名字是随意起的,默认的有两个:release 和 debug,如果想修改默认的打包配置就到这两个标签下改,如果想自己写一个就像上面这样写一个 myConfig 就可以了,里边写上用哪个 keystore 文件,keystore 的密码,然后不要忘了在 buildTypes 标签引用就可以了。
另外,如果在开发过程中需要使用发布模式运行,可以通过 Project Structure 设置,也可以在 signingConfigs 标签中配置,如下所示:
1 | android { |
项目中的 main 表示任何的 buildTypes 和 productFlavors 都会使用到,新增 debug 和 release 目录,以 GradleTest 为例,当版本为 debug 版本时,标题栏最左端会有绿色方块显示,而 release 版本则没有。所以 BuildTypesUtils 工具类代码要分成两份,其中 debug 版本下面的 BuildTypesUtils 是有绘制内容的,而 release 版本下面的 BuildTypesUtils是空实现的,需要注意的是,即便是空实现,但是 drawBadge 的方法还是要存在的,否则当运行 release 版本时,会提示方法找不到。
有时候还会需要内测版,提供一些测试后门和命令,可以做如下配置:
1 | buildTypes { |
同时增加 internal 目录,加入 BuildTypesUtils,在内测版本中,标题栏最左端会有黄色方块显示。
像 internal、release 是找不到方法的,但是 Gradle 会使用该方法的名称作为一个参数去调用别的方法。initWith debug 表示沿用 debug 的基本配置,当然可以在里面做一些自己的配置。
productFlavors 标签
productFlavors 标签可以用来做多渠道打包,以及免费版和收费版,中国版和国际版等等。配置如下:
1 | flavorDimensions 'price' |
此时 Build Variants 里面的选项变成了 productFlavors 和 buildTypes 的组合,像 freeDebug、paidInternal 等等。
productFlavors 标签也可以配置多个,如下所示:
1 | flavorDimensions 'price', 'nation' |
Build Variants 里面的选项就更多了。
dependencies 标签
整体概述
dependencies 这个标签下就是依赖的 jar 包、第三方库或者 library 工程,如下所示:
1 | implementation fileTree(dir: 'libs', include: ['*.jar']) |
这几行是新建工程默认生成的,其中第一行表明工程中要用到的 jar 包都可以放在 app 下的 libs 目录中,有了这一行配置,我们把 jar 包放在 libs 目录下即可,不需要再做额外的配置了。
下面以 v7 支持包进行说明:
1 | implementation 'com.android.support:appcompat-v7:23.4.0' |
表明到 maven 库中找 v7 支持包,单引号中内容被冒号分成了三个部分,com.android.support 是这个支持包在 maven 库中的存放路径,appcompat-v7 是这个包的名称,23.4.0是这个包的版本,有了这个配置,Gradle 工具就会到我们在全局 gradle 文件中指定的 maven 库中下载相应的支持包了,其中版本号可以不写23.4.0,写成23.4.+,意思就是如果支持包出了新版本,比如出了23.4.1,那么就用23.4这个分支下的最新版本,如果写成23.+,就是始终使用23这个版本下最新的分支版本,如果干脆写成一个+号,比如像下面这样
1 | implementation 'com.android.support:appcompat-v7:+' |
就是告诉编译器,始终使用最新版的 v7 支持包,比我们手动拷贝 jar 包或 aar 包要灵活多了,不过还是不建议这样写,因为 gradle 会始终访问网络去查询有没有新的包,而且也无法确定最新的包是不是一定适合,有没有兼容旧版的代码。
然后 dependencies 标签下还可以配置引用了哪些 Library 模块,如果 app 主模块有引用 Library 工程,就要在 dependencies 标签下指定 Library,像这样:
1 | implementation project(':my-library-module') |
project 后面括号中的内容要与 settings.gradle 文件中写的模块名称一致,也是以冒号开头,并且用单引号括起来。
compile、implementation 和 api
- implementation 不会传递依赖;
- compile / api 会传递依赖,在新版本中,compile 已被弃用。api 是 compile 的替代品,效果完全等同;
- 当依赖被传递时,二级依赖的改动会导致零级项目重新编译;当依赖不传递时,二级依赖的改动则不会导致零级项目重新编译,减少编译时间消耗。
项目中的其余配置文件
gradle.properties
从名字上就知道它是一个配置文件,这里可以定义一些常量供 build.gradle 使用,比如可以配置签名相关信息如 keystore 位置、密码、keyalias 等。
gradlew 和 gradlew.bat
这分别是 linux 下的 shell 脚本和 windows 下的批处理文件,它们的作用是根据 gradle-wrapper.properties 文件中的 distributionUrl 下载对应的 gradle 版本(如果当前电脑上没有的话)。这样就可以保证在不同的环境下构建时都是使用的统一版本的 gradle,即使该环境没有安装 gradle 也可以,因为 gradle wrapper 会自动下载对应的 gradle 版本。gradlew 的用法跟 gradle 一模一样,比如执行构建 gradle build 命令,你可以用 gradlew build。gradlew 即 gradle wrapper 的缩写。
Gradle Task
使用方法:gradlew taskName。
task 的结构如下:
1 | task taskName { |
下面是一个实际的例子,在 app 目录下增加 version.properties 文件,记录版本名称和版本号:
1 | #Mon Jan 27 20:06:40 CST 2020 |
并在 app 的 build.gradle 内增加如下代码:
1 | task bumpVersion(){ |
在 Android Studio 的 Terminal 内输入 gradlew bumpVersion,执行上面的 task,version.properties 中的 VERSION_CODE 加一。
如果将 doLast 和大括号去掉,那么不论在 Terminal 内输入 gradlew、gradlew bumpVersion 还是 gradlew clean 等等,上面的代码都会执行。task 任务代码要写在 doLast 或者 doFirst 内。事实上下面的两段代码是等效的:
1 | task bumpVersion(){ |
doFirst()、doLast() 和普通代码段的区别:
普通代码段在 task 创建过程中就会被执行,发生在 configuration 阶段;doFirst() 和 doLast() 在 task 执行过程中被执行,发生在 execution 阶段,在普通代码段后面执行。如果用户没有直接或间接执行 task,那么它的 doLast()、doFirst() 代码不会被执行,例如在增加版本号的任务中加入如下打印:
1 | task bumpVersion(){ |
在 Terminal 内输入 gradlew,那么只有 println “A” 和 println “B” 会执行,doLast 中增加版本号的方法是不会执行的,gradlew 并没有执行 task;如果在 Terminal 内输入 gradlew bumpVersion,增加了 taskName,则打印和增加版本号的方法都会执行。
doFirst() 和 doLast() 都是 task 代码,其中 doFirst() 是往队列的前面插入代码,doLast() 是往队列的后面插入代码。
task 的依赖:可以使用 task taskA(dependsOn: b) 的形式来指定依赖。指定依赖后,task 会在自己执行前先执行自己依赖的 task。在上面的例子中,我们模拟增加版本号之后代码提交 git 的方法,如下:
1 | task bumpVersion(){ |
由于 commitVersion 依赖 bumpVersion,这样在 Terminal 内输入 gradlew commitVersion,则会先执行 bumpVersion 再执行 commitVersion,即“增加版本号之后代码提交 git”。
gradle 执行的生命周期
gradle 执行有三个阶段:
- 初始化阶段:执行 settings.gradle,确定主 project 和子 project
- 定义阶段:执行每个 project 的 bulid.gradle,先执行 project#build.gradle 再执行 module#build.gradle,确定出所有 task 所组成的有向无环图;
- 执行阶段:按照上一阶段所确定出的有向无环图来执行指定的 task。
如果需要在各个阶段插入代码,在一二阶段之间,可以在 settings.gradle 的最后 插入,如下所示:
1 | include ':app', ':library1', ':library2' |
在二三阶段之间插入代码,需要写在 project#build.gradle 的 afterEvaluate 中,如下所示:
1 | buildscript { |
比如需要动态添加 Task 的情况,在有向无环图没有确定之前 Task 是不存在的,则需要在二三阶段之间插入代码。
参考资料:
gradle入门
小浩_w android signingConfigs打包配置
applixy 从Eclipse到AndroidStudio(四)Gradle基本配置
腾讯课堂 HenCoder