做为一个Android开发程序员,若是你的
build.gradle
都只能靠IDE生成或者从别的项目中复制粘贴来完成,那么你该好好的看完这篇文章,掌握一下你不知道的Gradle基础。html
文中的图片均来自于网络,侵删java
Gradle是一个基于JVM的构建工具,目前Android Studio中创建的工程都是基于gradle进行构建的。Gradle的与其余构建工具(ant、maven)的特性主要包括:android
Gradle的脚本都是配置型脚本。每一种脚本类型实际上都是某个具体的gradle的API中的类对象的委托,脚本执行对应的实际上是其委托的对象的配置。在一个完整的gradle的构建体系中,总共有三种类型的构建脚本,同时也分别对应着三种委托对象git
脚本类型 | 委托对象 |
---|---|
Init script | Gradle |
Settings script | Settings |
Build script | Project |
对应的就是上面的Init script,实际上就是Gradle
对象的委托,因此在这个init 脚本中调用的任何属性引用以及方法,都会委托给这个 Gradle
实例。程序员
Init script的执行发生在构建开始以前,也是整个构建最先的一步。github
每一个脚本的执行均可以配置当前脚本自己执行所须要的依赖项。Init scrip的配置以下:spring
// initscript配置块包含的内容就是指当前脚本自己的执行所须要的配置
// 咱们能够在其中配置好比依赖路径等等
initscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.apache.commons', name: 'commons-math', version: '2.0'
}
}
复制代码
要使用一个定义好的Init scrip,主要有如下几个方式apache
在执行gradle命令的时候,经过-I
或--init-script
命令选项指定脚本的路径api
这种方式能够针对具体的一次构建。tomcat
把一个init.gradle
文件放到 *USER_HOME*/.gradle/
目录
把一个文件名以.gradle
结尾的文件放到Gradle 分发包*GRADLE_HOME*/init.d/
目录内
以上的两种方式是全局的,对机器内的构建都会起做用
对应的是Settings script脚本类型,是Settings
对象的委托。在 脚本中调用的任何属性引用以及方法,都会委托给这个 Settings
实例。
Settings script的执行发生在gradle的构建生命周期中的初始化阶段。Settings脚本文件中声明了构建所须要的配置,并用以实例化项目的层次结构。在执行settings脚本并初始化Settings
对象实例的时候,会自动的构建一个根项目对象rootProject
并参与到整个构建当中。(rootProject
默认的名称就是其文件夹的名称,其路径就是包含setting脚本文件的路径)。
下面是一张关于Settings
对象的类图:
每个经过include
方法被添加进构建过程的project
对象,都会在settings脚本中创造一个ProjectDescriptor
的对象实例。
所以,在settings的脚本文件中,咱们能够访问使用的对象包括:
Settings
对象Gradle
对象ProjectDescriptor
对象在gradle中,只要根项目/任何子项目的目录中包含有构件文件,那么就能够在相应的位置运行构建。而判断一个构建是不是多项目的构建,则是经过寻找settings脚本文件,由于它指示了子项目是否包含在多项目的构建中。
查找settings文件的步骤以下:
当找到settings文件而且文件定义中包含了当前目录,则当前目录就会被认为是多项目的构建中的一部分。
对应的就是前面提到的Build script脚本类型,是gradle中Project
对象的委托。在脚本中调用的任何属性引用以及方法,都会委托给这个 Project
实例。
在build.gradle文件中有一个配置块buildScipt{}
是用于配置当前脚本执行所需的路径配置等的(与initScript形似)。
buildscript {
// 这里的repositories配置块要与Project实例当中的repositories区分开来
// 这里的repositories配置是指脚本自己依赖的仓库源,其委托的对象其实是ScriptHandler
repositories {
mavenLocal()
google()
jcenter()
}
// 与前面的repositories配置块相同,也要与Project当中的dependencies配置块区分开来
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
}
}
复制代码
这里补充关键的一点,在
build.gradle
文件中,无论buildScript{}
配置块被放在哪一个位置,它老是整个脚本文件中最早被执行的
每一个gradle构建都包含三个基本的构件块:
每一个构建包含至少一个project,进而又包含一个或者多个task。project和task暴露的属性(property)能够用来控制构建。
咱们对project的理解更多来源于项目目录中的build.gradle
文件(由于它其实就是project对象的委托)。Project
对象的类图以下所示:
在build.gradle脚本文件中,咱们不只能够对单独project进行配置,也能够定义project块的共有逻辑等,参考下面的定义。
常见的例子好比:
// 为全部项目添加仓库源配置
allprojects {
repositories {
jcenter()
google()
}
}
// 为全部子项目添加mavenPublish的配置块
subprojects {
mavenPublish {
groupId = maven.config.groupId
releaseRepo = maven.config.releaseRepo
snapshotRepo = maven.config.snapshotRepo
}
}
复制代码
任务是gradle构建的基础配置块之一,gradle的构建的执行就是task的执行。下面是task的类图。
当咱们定一个一个task的时候,会包含配置和动做两部分的内容。好比下面的代码示例:
task test{
println("这是配置")
doFirst{
// do something here
}
doLast(){
// do something here
}
}
复制代码
目前task的动做(action)声明主要包含两个方法:
这些动做是在gradle的构建生命周期中的执行阶段被调用。值得注意的是,一个task能够声明多个doFirst
和doLast
动做。也能够为一些已有的插件中定义的task添加动做。好比:
// 为test任务添加一个doLast的动做
test.doLast{
// do something here
}
复制代码
在task的定义之中,除了动做块之外的是配置块,咱们能够声明变量、访问属性、调用方法等等。这些配置块的内容发生在gradle的构建生命周期中的配置阶段。所以task中的配置每次都会被执行。(动做块只有在实际发生task的调用的时候才会执行)。
gradle中任务的执行顺序是不肯定的。经过task之间的依赖关系,gradle可以确保所依赖的task会被当前的task先执行。使用task的dependsOn()
方法,容许咱们为task声明一个或者多个task依赖。
task first{
doLast{
println("first")
}
}
task second{
doLast{
println("second")
}
}
task third{
doLast{
println("third")
}
}
task test(dependsOn:[second,first]){
doLast{
println("first")
}
}
third.dependsOn(test)
复制代码
默认状况下,咱们常见的task都是org.gradle.api.DefaultTask
类型。可是在gradle当中有至关丰富的task类型咱们能够直接使用。要更改task的类型,咱们能够参考下面的示例
task createDistribution(type:Zip){
}
复制代码
更多关于task的类型,能够参考gradle的官方文档
属性是贯穿在gradle构建始终的,用于帮助控制构建逻辑的存在。gradle中声明属性主要有如下两种方式:
gradle.properties
定义属性Gradle中不少模型类都提供了特别的属性支持,好比Project
.在gradle内部,这些属性会以键值对的形式存储。使用ext命名空间,咱们能够方便的添加属性。下面的方式都是支持的:
//在project中添加一个名为groupId的属性
project.ext.groupId="tech.easily"
// 使用ext块添加属性
ext{
artifactId='EasyDependency'
config=[
key:'value'
]
}
复制代码
值得注意的是,只有在声明属性的时候咱们须要使用
ext
命名空间,在使用属性的时候,ext
命名空间是能够省略的。
正如咱们常常在Android项目中看到的,咱们能够在项目的根目录下新建一个gradle.properties
文件,并在文件中定义简单的键值对形式的属性。这些属性可以被项目中的gradle脚本所访问。以下所示:
# gradle.properties
# 注意文件的注释是以#开头的
groupId=tech.easily
artifactId=EasyDependency
复制代码
有的时候,咱们可能须要在代码中动态的建立属性文件并读取文件中的属性(好比自定义插件的时候),咱们可使用java.util.Properties
类。好比:
void createPropertyFile() {
def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
def defaultProps = new Properties()
if (!localPropFile.exists()) {
localPropFile.createNewFile()
defaultProps.setProperty("debuggable", 'true')
defaultProps.setProperty("groupId", GROUP)
defaultProps.setProperty("artifactId", project.name)
defaultProps.setProperty("versionName", VERSION_NAME)
defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")
} else {
localPropFile.withInputStream { stream ->
defaultProps.load(stream)
}
}
}
复制代码
关于属性很重要的一点是属性是能够继承的。在一个项目中定义的属性会自动的被其子项目继承,无论咱们是用以上哪一种方式添加属性都是适用的。
前面说起到了gradle中多种脚本类型,而且他们都在不一样的生命周期中被执行。
在gradle构建中,构建的生命周期主要包括如下三个阶段:
初始化(Initialization)
如前文所述,在这个阶段,settings脚本会被执行,从而Gradle会确认哪些项目会参与构建。而后为每个项目建立 Project
对象。
配置(Configuration)
配置 Initialization 阶段建立的Project
对象,全部的配置脚本都会被执行。(包括Project
中定义的task的配置块也都会被执行)
执行(Configuration)
这个阶段Gradle会确认哪些在 Configuration 阶段建立和配置的 Task 会被执行,哪些 Task会被执行取决于gradle
命令的参数以及当前的目录,确认以后便会执行
在gradle的构建过程当中,gradle为咱们提供了很是丰富的钩子,帮助咱们针对项目的需求定制构建的逻辑,以下图所示:
要监听这些生命周期,主要有两种方式:
关于可用的钩子能够参考Gradle
和Project
中的定义,经常使用的钩子包括:
beforeProject()/afterProject()
等同于Project
中的beforeEvaluate
和afterEvaluate
settingsEvaluated()
settings脚本被执行完毕,Settings
对象配置完毕
projectsLoaded()
全部参与构建的项目都从settings中建立完毕
projectsEvaluated()
全部参与构建的项目都已经被评估完
whenReady()
task图生成。全部须要被执行的task已经task之间的依赖关系都已经确立
在前面说起的Gradle的主要特性之中,其中的一点就是强大的依赖管理。Gradle中具有丰富的依赖类型,兼容多种依赖仓库。同时Gradle中的每一项依赖都是基于特定的范围(scope)进行分组管理的。
在gradle中添加为项目添加依赖的方式以下所示:
// build.gradle
// 添加依赖仓库源
repositories {
google()
mavenCentral()
}
// 添加依赖
// 依赖类型包括:文件依赖、项目依赖、模块依赖
dependencies {
// local dependencies.
implementation fileTree(dir: 'libs', include: ['*.jar'])
...
}
复制代码
Gradle中的依赖类型有四类:
模块依赖
这是gradle中比较常见的依赖类型, 它一般指向仓库中的一个构件,以下所示:
dependencies {
runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
runtime 'org.springframework:spring-core:2.5',
'org.springframework:spring-aop:2.5'
runtime(
[group: 'org.springframework', name: 'spring-core', version: '2.5'],
[group: 'org.springframework', name: 'spring-aop', version: '2.5']
)
runtime('org.hibernate:hibernate:3.0.5') {
transitive = true
}
runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
transitive = true
}
}
复制代码
模块依赖对应于gradle的API中的 ExternalModuleDependency
对象
文件依赖
dependencies {
runtime files('libs/a.jar', 'libs/b.jar')
runtime fileTree(dir: 'libs', include: '*.jar')
}
复制代码
项目依赖
dependencies {
compile project(':shared')
}
复制代码
项目依赖对应于gradle的API中的 ProjectDependency
对象
特定的Gradle发行版依赖
dependencies {
compile gradleApi()
testCompile gradleTestKit()
compile localGroovy()
}
复制代码
gradle中项目的每一项依赖都是应用于一个特定的范围的,在gradle中用 Configuration
对象表示。每个Configuration
对象都会有一个惟一的名称。Gradle的依赖配置管理以下所示:
在gradle中,自定义Configuration
对象是很是简单的,同时定义本身的Configuration
对象的时候,也能够继承于已有的Configuration
对象,以下所示:
configurations {
jasper
// 定义继承关系
smokeTest.extendsFrom testImplementation
}
repositories {
mavenCentral()
}
dependencies {
jasper 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.2'
}
复制代码
在实际的项目依赖管理中存在这样的一种依赖关系:
在处理上面这种传递性依赖的时候,gradle提供了强大的管理功能
依赖约束能够帮助咱们控制传递性依赖以及自身的依赖的版本号(版本范围),好比:
dependencies {
implementation 'org.apache.httpcomponents:httpclient'
constraints {
// 这里httpclient是项目自己的依赖
// 这个约束表示,不论是项目自己的依赖是仍是传递依赖都强制使用这个指定的版本号
implementation('org.apache.httpcomponents:httpclient:4.5.3') {
because 'previous versions have a bug impacting this application'
}
// commons-codec并无被声明为项目自己的依赖
// 因此仅当commons-codec是传递性依赖的时候这段逻辑才会被触发
implementation('commons-codec:commons-codec:1.11') {
because 'version 1.9 pulled from httpclient has bugs affecting this application'
}
}
}
复制代码
有的时候,咱们所依赖的项目/模块会引入多个传递性依赖。而其中部分的传递性依赖咱们是不须要的,这时候可使用exclude
排除部分的传递性依赖,以下所示:
dependencies {
implementation('log4j:log4j:1.2.15') {
exclude group: 'javax.jms', module: 'jms'
exclude group: 'com.sun.jdmk', module: 'jmxtools'
exclude group: 'com.sun.jmx', module: 'jmxri'
}
}
复制代码
Gradle经过选择依赖关系图中找到的最新版原本解决任何依赖版本冲突。 但是有的时候,某些项目会须要使用一个较老的版本号做为依赖。这时候咱们能够强制指定某一个版本。例如:
dependencies {
implementation 'org.apache.httpcomponents:httpclient:4.5.4'
// 假设commons-codec的最新版本是1.10
implementation('commons-codec:commons-codec:1.9') {
force = true
}
}
复制代码
要注意的是,若是依赖项目中使用了新版本才有的api,而咱们强制使用了旧版本的传递依赖以后,会引发运行时的错误
dependencies {
implementation('com.google.guava:guava:23.0') {
transitive = false
}
}
复制代码
依赖关系解析规则提供了一种很是强大的方法来控制依赖关系解析过程,并可用于实现依赖管理中的各类高级模式。好比:
统一构件组的版本
不少时候咱们依赖一个公司的库会包含多个module,这些module通常都是统一构建、打包和发布的,具有相同的版本号。这个时候咱们能够经过控制依赖关系的解析过程作到版本号统一。
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.gradle') {
details.useVersion '1.4'
details.because 'API breakage in higher versions'
}
}
}
复制代码
处理自定义的版本scheme
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.version == 'default') {
def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
details.useVersion version.version
details.because version.because
}
}
}
def findDefaultVersionInCatalog(String group, String name) {
//some custom logic that resolves the default version into a specific version
[version: "1.0", because: 'tested by QA']
}
复制代码
关于更多依赖关系解析规则的使用实例能够参考gradle的API中的 ResolutionStrategy
依赖关系的替换规则和上面的依赖关系解析规则有点类似。实际上,依赖关系解析规则的许多功能能够经过依赖关系替换规则来实现。依赖关系的替换规则容许项目依赖(Project Dependency)和模块依赖(Module Dependency)被指定的替换规则透明地替换。
// 使用项目依赖替换模块依赖
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module("org.utils:api") with project(":api") because "we work with the unreleased development version"
substitute module("org.utils:util:2.5") with project(":util")
}
}
// 使用模块依赖替换项目依赖
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute project(":api") with module("org.utils:api:1.3") because "we use a stable version of utils"
}
}
复制代码
除了上面两种以外,还有其余三种的依赖关系规则处理。由于没有实际使用过,这里不过多阐述,想了解更多能够查看官方的文档Customizing Dependency Resolution Behavior
插件开发是gradle灵活的构建体系中的一个强大工具。经过gradle中的Plugin
API,咱们能够自定义插件,把一些通用的构建逻辑插件化并普遍的运用。好比Android项目中都会使用的:com.android.application
,kotlin-android
,java
等等。
网上关于插件开发的文章已经不少,这里再也不赘述。这里推荐我写的一个Gradle插件,也是在我彻底看了gradle的官方文档以后,结合前面说起到的依赖管理的知识写的:
一个帮助提升组件化开发效率的gradle插件,提供的功能包括:
全文基本是在看了gradle的官方文档及相关资料以后,按照本身的思路的整理和总结。关于gradle的使用和问题欢迎一块儿讨论。