相信对于任何一位安卓开发工程师来说,Gradle一定不陌生。但是很多人甚至是很多年开发经验的高级工程师对Gradle仍然不甚了解,市面上涉及Gradle的文章大多数讲解的也不是很系统。本专栏旨在系统的梳理下Gradle的相关知识点,希望能对你有所帮助。
“Gradle is an open-source build automation system that builds upon the concepts of Apache Ant and Apache Maven and introduces a Groovy-based domain-specific language (DSL) instead of the XML form used by Apache Maven for declaring the project configuration.[1] Gradle uses a directed acyclic graph ("DAG") to determine the order in which tasks can be run.”——维基百科对Gradle的定义。
翻译过来就是:“Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。”
可以从三个角度来理解:
1. Gradle 是一个自动化构建工具
Gradle 是通过组织一系列 Task 来最终完成自动化构建的,以生成一个可用的 apk 为例,整个过程要经过 资源的处理,javac 编译,dex 打包,apk 打包,签名等步骤,每个步骤就对应到 Gradle 里的一个 Task。
2. 编写 Gradle 脚本可以使用 Groovy 或者 Kotlin DSL
Gradle 脚本 使用 Groovy 或者 Kotlin 编写,不过目前还是 Groovy 居多。DSL 也就是 Domain Specific Language 的简称,是为了解决某一类任务专门设计的计算机语言。与 GPL(General-Purpose Language,比如 Java) 相比起来,DSL 使用简单,定义比较简洁,比起配置文件,DSL 又可以实现语言逻辑。对 Gradle 脚本来说,他实现了简洁的定义,又有充分的语言逻辑,以 android {} 为例,这本身是一个函数调用,参数是一个闭包,但是这种定义方式明显要简洁很多。
3. Gradle 基于 Groovy 编写,而 Groovy 是基于 JVM 语言
Gradle 使用 Groovy 编写,Groovy 是基于 JVM 的语言,所以本质上是面向对象的语言,面向对象语言的特点就是一切皆对象,所以,在 gradle 里,.gradle 脚本的本质就是类的定义,一些配置项的本质都是方法调用,参数是后面的 {} 闭包。比如 build.gradle 对应 Project 类,buildScript 对应 Project.buildScript 方法。
settings.gradle 是负责配置项目的脚本。对应 Settings 类,Gradle 构建过程中,会根据 settings.gradle 生成 Settings 的对象。
其中几个主要的方法有:
include ':app'
//指定子模块的位置,使用 project 方法获取 Project 对象,设置其 projectDir 参数
project(':app').projectDir = new File('./app')
build.gradle 负责整体项目的一些配置,对应的是 Project 类,gradle 构建的时候,会根据 build.gradle 生成 Project 对象。Project 其实是一个接口,真正的实现类是 DefaultProject。
其中几个主要方法有:
buildscript { // 配置项目的 classpath
repositories { // 项目的仓库地址,会按顺序依次查找
google()
jcenter()
mavenLocal()
}
dependencies { // 项目的依赖
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.zy.plugin:myplugin:0.0.1'
}
}
allprojects { // 子项目的配置
repositories {
google()
jcenter()
mavenLocal()
}
}
build.gradle 是子项目的配置,对应的也是 Project 类。
子项目和根项目的配置是差不多的,不过在子项目里可以看到有一个明显的区别,就是引用了一个插件 apply plugin "com.android.application",后面的 android dsl 就是 application 插件的 extension。关于 android plugin dsl 可以看 android-gradle-dsl(需要科学上网)。
其中几个主要方法有:
apply plugin: 'com.android.application' // 引入 android gradle 插件
android { // 配置 android gradle plugin 需要的内容
compileSdkVersion 26
defaultConfig { // 版本,applicationId 等配置
applicationId "com.zy.easygradle"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions { // 指定 java 版本
sourceCompatibility 1.8
targetCompatibility 1.8
}
// flavor 相关配置
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
}
// 项目需要的依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) // jar 包依赖
implementation 'com.android.support:appcompat-v7:26.1.0' // 远程仓库依赖
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation project(':module1') // 项目依赖
}
gradlew / gradlew.bat 这个文件用来下载特定版本的 gradle 然后执行的,就不需要开发者在本地再安装 gradle 了。
这样做有什么好处呢?开发者在本地安装 gradle,会碰到的问题是不同项目使用不同版本的 gradle 怎么处理,用 wrapper 就很好的解决了这个问题。gradle wrapper 一般下载在 GRADLE_CACHE/wrapper/dists 目录下。
gradle/wrapper/gradle-wrapper.properties 是一些 gradlewrapper 的配置,其中用的比较多的就是 distributionUrl,可以执行 gradle 的下载地址和版本。
gradle/wrapper/gradle-wrapper.jar 是 gradlewrapper 运行需要的依赖包。
在 gradle 里,有一种 init.gradle 比较特殊,这种脚本会在每个项目 build 之前先被调用,可以在其中做一些整体的初始化操作,比如配置 log 输出等。
使用 init.gradle 的方法:
Gradle 构建分为三个阶段:
1. 初始化阶段
初始化阶段主要做的事情是解析 settings.gradle 来获取模块信息,明确有哪些项目需要被构建,然后为对应的项目创建 Project 对象。
2. 配置阶段
配置阶段主要做的事情是对上一步创建的项目进行配置,这时候会执行 build.gradle 脚本,并且会生成要执行的 Task。
3. 执行阶段
执行阶段主要做的事情就是执行 Task,进行主要的构建工作。
在 gradle 3.4 里引入了新的依赖配置,如下:
新配置 | 弃用配置 | 行为 | 作用 |
---|---|---|---|
implementation | compile | 依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。 对于大型多项目构建,使用 implementation 而不是 api/compile 可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。 大多数应用和测试模块都应使用此配置。 | implementation 只会暴露给直接依赖的模块,使用此配置,在模块修改以后,只会重新编译直接依赖的模块,间接依赖的模块不需要改动 |
api | compile | 依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。 此配置的行为类似于 compile(现在已弃用),一般情况下,您应当仅在库模块中使用它。 应用模块应使用 implementation,除非您想要将其 API 公开给单独的测试模块。 | api 会暴露给间接依赖的模块,使用此配置,在模块修改以后,模块的直接依赖和间接依赖的模块都需要重新编译 |
compileOnly | provided | 依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。 此配置的行为类似于 provided(现在已弃用)。 | 只在编译期间依赖模块,打包以后运行时不会依赖,可以用来解决一些库冲突的问题 |
runtimeOnly | apk | 依赖项仅在运行时对模块及其消费者可用。 此配置的行为类似于 apk(现在已弃用)。 | 只在运行时依赖模块,编译时不依赖 |
现在假设项目里有三个模块:app,module1, module2
其依赖关系如下:
1. implementation 依赖
当 module1 使用 implementation 依赖 module2 时,在 app 模块中无法引用到 Module2Api 类。
2. api 依赖
当 module1 使用 api 依赖 module2 时,在 app 模块中可以正常引用到 Module2Api 类。
3. compileOnly 依赖
当 module1 使用 compileOnly 依赖 module2 时,在编译阶段 app 模块无法引用到 Module2Api 类,module1 中正常引用,但是在运行时会报错。
反编译打包好的 apk,可以看到 Module2Api 是没有被打包到 apk 里的。
4. runtimeOnly 依赖
当 module1 使用 runtimeOnly 依赖 module2 时,在编译阶段,module1 也无法引用到 Module2Api。
Flavor是在Gradle中配置多渠道的打包的工具,它将 debug 和 release 维度进一步扩大。
在 android gradle plugin 3.x 之后,每个 flavor 必须对应一个 dimension,可以理解为 flavor 的分组,然后不同 dimension 里的 flavor 两两组合形成一个 variant。
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
那么生成的 variant 对应的就是 bigBlue,bigRed,smallBlue,smallRed。
每个 variant 可以对应的使用 variantImplementation 来引入特定的依赖,比如:bigBlueImplementation,只有在 编译 bigBlue variant的时候才会引入。