Gradle基础
Groovy语法
- 类型定义
int vs = 1 |
- 方法
task invokeMethod << {//这里的<<相当于dolast 最后执行 |
- 闭包
闭包简单说就是代码块,如下:
def v = { |
其中定义的v就为闭包,testMethod 为一个方法,传入参数Closure为闭包,然后调用闭包。
闭包可以作为参数传递,以集合的each方法为例:
list.each({println it})//传统的参数传递写法大概如此 |
- 向闭包传递参数
task helloClosure << { |
- 闭包委托
Groovy支持闭包方法的委托。有三种委托分别是:
1. thisObject
2. owner
3. delegate
其中默认情况下owner和delegate的作用相同,但delegate可以更改。thisObject和this对象也是相等的。由于优先级的不同。三种委托的执行顺序是:thisObject -> owner -> delegate
task configClosure << { |
上面的了例子中我们设置了代理对象为当前创建的实例,并且设置了委托模式优先,所以我们在使用person方法创建一个person实例的时候,可以直接对该Person对象进行配置。在Gradle中还有很多类似的用法,基本上都是使用delegate来进行配置等操作
Gradle命令行
- -i:Gradle默认不会输出很多信息,你可以使用-i选项改变日志级别为INFO
- -s:如果运行时错误发生打印堆栈信息
- -q:只打印错误信息 使用命令行
- -?-h,–help:打印所有的命令行选项
- -b,–build-file:Gradle默认执行build.gradle脚本,如果想执行其他脚本可以使用这个命令,比如gradle -b test.gradle –offline:在离线模式运行build,Gradle只检查本地缓存中的依赖
- -D, –system-prop:Gradle作为JVM进程运行,你可以提供一个系统属性比如:- Dmyprop=myValue
- -P,–project-prop:项目属性可以作为你构建脚本的一个变量,你可以传递一个属性值给 build脚本,比如:-Pmyprop=myValue
- tasks:显示项目中所有可运行的任务
- properties:打印你项目中所有的属性值
Projects 和 tasks
- Task依赖
task dependsOnTesk { |
- 自定义属性
Project和Task都支持自定义属性,通过ext属性来完成添加和读取
//自定义一个project属性 |
任务之间通过依赖来控制任务的执行时序,在创建task 的时候通过dependsOn可以指定其依赖的任务
Android Gradle
下图是一个名为ImageViewShow的Android工程结构图,包含两个module和Gradle相关文件
gralde的项目配置是先识别 settings.gradle,然后在配置各个build.gradle。一个Android工程的Gradle包含如下内容(按上图顺序介绍):
- 根项目的builde.gradle文件 配置项目的整体属性,比如指定使用的代码仓库、依赖的Gradle插件版本等等。
- 子module的build.gradle文件 配置当前Module的编译参数 此处对应有app和test的两个module
- Gradle Version的配置文件
- 子module的规则配置文件
- 根project的properties文件 配置Gradle的编译参数
- 根project的Setting文件 配置Gradle的多项目管理
- 一般用来存放该Android项目的私有属性配置,比如Android项目的SDK路径
现在来验证一下gradle文件的配置顺序,在setting和build文件里添加一句输出语句:println “···”,使用./graldew命令运行:
显然,gradle的执行顺序按照setting -> project build.gradle -> project.app build.gradle -> project.test build.gradle 的顺序来执行的
项目build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
模块build.gradle
println "app build.gradle" |
- Gradle的Android插件类型
apply引入的插件id为com.android.application,说明当前模块是一个应用程序模块,Gradle的Android插件有多个类型分别为:
- 应用程序插件,插件id为com.android.application,会生成一个APK。
- 库插件,插件id为com.android.library,会生成一个AAR,提供给其他应用程序模块用。
- 测试插件,插件id为com.android.test,用于测试其他的模块。
- feature插件,插件id为com.android.feature,创建Android Instant App时需要用到的插件。
- Instant App插件,插件id为com.android.instantapp,是Android Instant App的入口
- defaultConfig 常用配置补充
- proguardFile 代码混淆使用的配置文件
- proguardFiles 配置的多个ProGuard文件
- signingConfig 配置默认的签名信息
- buildTypes
applicationIdSuffix | 配置applicationId的后缀 |
---|---|
debuggable | 表示是否支持断点调试 |
jniDebuggable | 表示是否可以调试NDK代码 |
buildConfigField | 配置不同的开发环境,比如测试环境和正式环境 |
shrinkResources | 是否自动清理未使用的资源,默认值为false |
zipAlignEnabled | 是否开启开启zipalign优化,提高apk运行效率 |
proguardFile | ProGuard混淆所使用的ProGuard配置文件 |
proguardFiles | 同事配置多个ProGuard配置文件 |
signingConfig | 配置默认的签名信息 |
multiDexEnabled | 是否启用自动拆分多个Dex的功能 |
- 其他配置
全局配置build.gradle
如果有多个module的配置是一样的,可以将这些配置提取出来,也就是使用全局配置。全局配置有多种方式,这里介绍其中的两种
ext块定义属性
在项目build.gradle中使用ext块,如下所示:
ext{ |
在某个module的build.gradle中使用配置:
apply plugin: 'com.android.application' |
这里利用的是上面介绍过的ext自定义属性来实现的,但是如果都把ext代码块写在项目根build.gradle文件里,那会十分臃肿难以维护,所以另一种方式就是将这里ext代码块写在单独的文件里,使用时引入这个文件就好了。
使用config.gradle
- 在项目根目录下创建config.gradle文件:
ext{ |
- 在根项目build.gradle文件里使用**apply from: “config.gradle”,这样项目所有module都能使用config.gradle的参数
- 最后在module里使用,使用方式与上一种方式相同
Gradle构建生命周期
主要生命周期:
初始化阶段:负责判断有多少个Projects参与构建
配置阶段:负责对初始化阶段创建的Projects完成配置
执行阶段:根据配置阶段的配置执行任务
private void doBuildStages(Stage upTo) { |
初始化阶段
这个阶段中,会读取根工程中的 setting.gradle 中的 include 信息,确定有多少工程加入构建,然后,会为每一个项目(build.gradle 脚本文件)创建一个个与之对应的 Project 实例,最终形成一个项目的层次结构。 与初始化阶段相关的脚本文件是 settings.gradle,而一个 settings.gradle 脚本对应一个 Settings 对象,我们最常用来声明项目的层次结构的 include 就是 Settings 对象下的一个方法,在 Gradle 初始化的时候会构造一个 Settings 实例对象,以执行各个 Project 的初始化配置
settings.gradle
在 settings.gradle 文件中,我们可以 在 Gradle 的构建过程中添加各个生命周期节点监听:
配置阶段
配置阶段的任务是 执行各项目下的 build.gradle 脚本,完成 Project 的配置,与此同时,会构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task。而在配置阶段执行的代码通常来说都会包括以下三个部分的内容:
- build.gradle
- 闭包
- Task中的配置段语句
任何Gradle命令被执行 初始化和配置阶段的代码都会被执行
执行阶段
配置阶段结束后,Gradle 会根据各个任务 Task 的依赖关系来创建一个有向无环图,我们可以通过 Gradle 对象的 getTaskGraph 方法来得到该有向无环图 => TaskExecutionGraph,并且,当有向无环图构建完成之后,所有 Task 执行之前,我们可以通过 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 来接收相应的通知:
gradle.getTaskGraph().addTaskExecutionGraphListener(newTaskExecutionGraphListener(){ |
Hook Gradle生命周期
整个 Gradle 生命周期的流程包含如下 四个部分:
- 解析 settings.gradle 来获取模块信息,这是初始化阶段。
- 配置每个模块,配置的时候并不会执行 task。
- 配置完了以后,有一个重要的回调 project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行 task 了。
- 执行指定的 task 及其依赖的 task
Hook注意点:
Hook 点对应的监听器一定要在回调的生命周期之前添加
注册了多个 project.afterEvaluate 回调,那么执行顺序将与注册顺序保持一致
获取构建耗时
在setting.gradle中加入:
Gradle 中,执行每一种类型的配置脚本就会创建与之对应的实例,而在 Gradle 中如 三种类型的配置脚本,如下所示:
Init Scrpit
:对应一个 Gradle 实例,它在构建初始化时创建,整个构建执行过程中以单例形式存在Build Scrpit
:对应一个 Project 实例,即每个 build.gradle 都会转换成一个 Project 实例。- Settings Scrpit`:对应一个 Settings 实例,即每个 settings.gradle 都会转换成一个 Settings 实例。
一个 Gradle 构建流程中会由一至多个 project 实例构成,而每一个 project 实例又是由一至多个 task 构成。
Project
Project 是 Gradle 构建整个应用程序的入口,所以它非常重要。每一个 build.gradle 都有一个与之对应的 Project 实例,而在 build.gradle 中,我们通常都会配置一系列的项目依赖,比如第三方库Glide:
implementation 'com.github.bumptech.glide:glide:4.8.0' |
类似于 implementation、api 这种依赖关键字,在本质上它就是一个方法调用,在上面,我们使用 implementation() 方法传入了一个 map 参数,参数里面有三对 key-value,完整写法如下所示:
implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0' |
当我们使用 implementation、api 依赖对应的 aar 文件时,Gradle 会在 repository 仓库 里面找到与之对应的依赖文件,你的仓库中可能包含 jcenter、maven 等一系列仓库,而每一个仓库其实就是很多依赖文件的集合服务器, 而他们就是通过上述的 group、name、version 来进行归类存储的
API介绍
Project 中有很多的 API,根据它们的 属性和用途 我们可以将其分解为 六大部分:
![Project API](https://cdn.jsdelivr.net/gh/demoless/imgs/noteProject API.png)
1)、Project API
:让当前的 Project 拥有了操作它的父 Project 以及管理它的子 Project 的能力
2)、Task 相关 API
:为当前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常重要,我们将放到第四章来进行讲解
3)、Project 属性相关的 Api
:Gradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我们拥有了为 Project 添加额外属性的能力
4)、File 相关 Api
:Project File 相关的 API 主要用来操作我们当前 Project 下的一些文件处理
5)、Gradle 生命周期 API
:即我们在第二章讲解过的生命周期 API
6)、其它 API
:添加依赖、添加配置、引入外部文件等等零散 API 的聚合
核心API
每一个 Groovy 脚本都会被编译器编译成 Script 字节码,而每一个 build.gradle 脚本都会被编译器编译成 Project 字节码,所以我们在 build.gradle 中所写的一切逻辑都是在 Project 类内进行书写的
getAllprojects
build.gradle文件中获取所有project实例:
this.getProjects() |
执行./gradlew clean命令,运行结果如下:
rootProject 与各个子工程组成了一个树形结构,但是这颗树的高度也仅仅被限定为了两层
getSubprojects
getSubProjects用于获取工程下所有子工程的实例:
this.getSubProjects() |
运行结果:
getParent
获取当前 project 的父类,需要注意的是,如果我们在根工程中使用它,获取的父类会为 null,因为根工程没有父类
在app模块下的build.gradle文件下:
this.parent() |
运行结果:
getRootProject
getRootProject 即可在任意 build.gradle 文件获取当前根工程的 project 实例
project
project 表示的是 指定工程的实例,然后在闭包中对其进行操作。在使用之前,我们有必要看看 project 方法的源码:
project 方法中两个参数,一个是指定工程的路径,另一个是用来配置该工程的闭包,如何灵活使用project:
project("app") { Project project -> |
subprojects
subprojects 可以统一配置当前project下的所有子project:
//为所有的aar文件设置配置脚本 |
文件API
路径获取API
- getRootDir()
- getProjectDir()
- getBuildDir()
println "the root file path is: ${getRootDir().absolutePath}" |
运行效果:
Task
def startBuildTime,endBuildTime |
Task依赖和执行顺序
Task的执行顺序有以下三种:
dependsOn 强依赖
- 静态依赖
task task1 { |
- 动态依赖
//动态依赖方式 |
通过 Task 指定输入输出
通过 Task 来指定输入输出,使用这种方式我们可以 高效地实现一个 自动维护版本发布文档的 gradle 脚本
Gradle原理
由gradle-wrapper到gradle的整个周期图: