前言
Gradle 它是一个基于 JVM 的新一代构建工具,这个系列会针对 Android 开发来对 Gradle 的知识进行精简讲解。Gradle 目前已经应用于多个 Android 开发的技术体系中,比如构建系统、插件化、热修复和组件化等等。
一、Gradle 是什么
Gradle 是一个构建工具,构建工具用于实现项目自动化,是一种可编程的工具,你可以用代码来控制构建流程最终生成可交付的软件。构建工具可以帮助你创建一个重复的、可靠的、无需手动介入的、不依赖于特定操作系统和IDE 的构建。这么说可能有些抽象,这里拿APK的构建过程来分析说明构建工具的作用。
Apk 的构建过程
APK 的构建过程可以根据官方提供的流程图如下图所示:
这个 APK 构建的过程主要分为以下几步:
-
通过 AAPT(Android Asset Packaging Tool) 打包 res 资源文件,比如 AndroidManifest.xml、xml 布局文件等,并将这些 xml 文件编译为二进制,其中 assets 和 raw 文件夹的文件不会被编译为二进制,最终会生成R.java 和 resources.arsc 文件。
-
AIDL 工具会将所有的 aidl 接口转化为对应的 Java 接口。
-
所有的 Java 代码,包括 R.java 和 Java 接口都会被 Java 编译器编译成 .class 文件。
-
Dex 工具会将上一步生成的 .class 文件、第三库和其他 .class 文件编译成 .dex 文件。
-
上一步编译生成的 .dex 文件、编译过的资源、无需编译的资源(如图片等)会被 ApkBuilder 工具打包成 APK 文件。
-
使用 Debug Keystore 或者 Release Keystore 对上一步生成的 APK 文件进行签名。
-
如果是对 APK 正式签名,还需要使用 zipalign 工具对 APK 进行对齐操作,这样应用运行时会减少内存的开销。
从以上步骤可以看出,APK 的构建过程是比较繁琐的,而且这个构建过程又是时常重复的,如果没有构建工具,手动去完成构建工作,无疑对于开发人员是个折磨,也会产生诸多的问题,导致项目开发周期变长。
(1)Gradle 是一个自动化的构建工具
Gradle 是通过组织一系列 task 来最终完成自动化构建的,所以 task 是 gradle 里最重要的概念。
(2)Gradle 脚本使用了 Groovy 或者 Kotlin 的 DSL
Gradle 使用 Groovy 或者 Kotlin 编写,不过目前还是 Groovy 居多。
那什么是 DSL 呢?DSL 也就是 Domain Specific Language 的简称,是为了解决某一类任务专门设计的计算机语言。
DSL 相对应的是 GPL (General-Purpose Language),比如 java。
与 GPL 相比起来,DSL 使用简单,定义比较简洁,比起配置文件,DSL 又可以实现语言逻辑。
对 Gradle 脚本来说,他实现了简洁的定义,又有充分的语言逻辑,以 android {} 为例,这本身是一个函数调用,参数是一个闭包,但是这种定义方式明显要简洁很多。
(3)Gradle 基于 Groovy 编写,而 Groovy 是基于 Jvm 语言
Gradle 使用 Groovy 编写,Groovy 是基于 Jvm 的语言,所以本质上是面向对象的语言,面向对象语言的特点就是一切皆对象。
所以,在 Gradle 里,.gradle 脚本的本质就是类的定义,一些配置项的本质都是方法调用,参数是后面的 {} 闭包。
比如 build.gradle 对应 Project 类,buildScript 对应 Project.buildScript 方法。
二、Gradle 的任务
为了更好的理解 Gradle 命令行,这里简单的介绍下 Gradle 的任务,包括创建任务、任务依赖、 动态定义任务和任务的分组和描述。
说明: 这一节的所有代码都是在Android Studio 的 Module 模块的 build.gradle 文件中编写测试。
2.1 创建任务
默认创建的 task 继承自 DefaultTask。
(1)使用 task + 任务名 方式创建
task hello{
doLast{
println 'hello world!'
}
}
运行结果如下图所示:上面的代码中,task 代表一个独立的原子性操作,比如复制一个文件,编译一次 Java 代码,这里我们定义一个名为 hello 的任务。doLast 代表 task 执行的最后一个action,通俗来讲就是 task 执行完毕后会回调 doLast 中的代码。 第二种运行方式如下如所示:
在 Gradle 中找到 app ——> Tasks —— > other —— > hello。
(2)直接用任务名称创建
def Task hello = task(hello)
hello.doLast{
println 'hello world!'
}
(3)任务名称 + 任务配置创建 (其中group为任务配置项,它代表了分组)
def Task hello=task(hello,group:BasePlugin.BUILD_GROUP)
hello.doLast{
println "hello world"
}
(4)TaskContainer 的create 方法创建
tasks.create(name: 'hello').doLast {
println "hello world"
}
2.2 任务依赖
任务依赖会决定任务运行的先后顺序,被依赖的任务会在定义依赖的任务之前执行。创建任务间的依赖关系如下所示。
task hello {
doLast {
println 'Hello task'
}
}
task go(dependsOn: hello) {
doLast {
println "go task"
}
}
在 hello 任务的基础上增加了一个名为 go 的任务,通过 dependsOn 来指定依赖的任务为 hello,因此 go 任务运行在 hello 之后。运行结果如下图所示:
2.3 动态定义任务
动态定义任务指的是在运行时来定义任务的名称,如下所示:
3.times {number ->
task "task$number"{
doLast{
println "task $number"
}
}
}
这里用到了 Groovy 语法,times 是Groovy 在 java.lang.Number 中拓展的方法,是一个定时器。3.times 中循环创建了三个新任务,隐式变量 number 的值为 0,1,2,任务的名称由 task 加上 number 的值组成,达到了动态定义任务的目的。
同步(Sync Now)之后会创建三个task,如下图所示:
2.4 任务的描述与分组
Gradle 有任务组的概念,可以为任务配置分组和描述,以便于更好的管理任务,拥有良好的可读性,下面为 hello 任务添加分组和描述。
task hello {
group = 'build'
description = 'hello world'
doLast {
println "task group:${group}"
println "task description:${description}"
}
}
当给任务添加分组之后,那么任务就会在相应的分组中显示。对于hello 任务就会显示在 Gradle -> app -> Tasks -> build 中,如下图所示:
也可以使用如下方式来为任务添加分组与描述,代码如下所示:
def Task hello = task(hello)
hello.group = BasePlugin.BUILD_GROUP
hello.description = 'hello world'
hello.doLast {
println "task group:${group}"
println "task description:${description}"
}
2.5 Task 的方法分类
-
Task 行为 Task.doFirst Task.doLast
-
Task 依赖顺序 Task.dependsOn Task.mustRunAfter Task.shouldRunAfter Task.finalizedBy
-
Task 的分组描述 Task.group Task.description
-
Task 是否可用 Task.enabled
-
Task 输入输出 gradle 会比较 task 的 inputs 和 outputs 来决定 task 是否是最新的,如果 inputs 和 outputs 没有变化,则认为 task 是最新的,task 就会跳过不执行 Task.inputs Task.outputs
-
Task 是否执行 可以通过指定 Task.upToDateWhen = false 来强制 task 执行 Task.upToDateWhen
三、Gradle 日志级别
Gradle 与 Android 一样也定义了日志级别,如下表所示:
级别 | 作用 |
---|---|
ERROR | 错误消息 |
QUIET | 重要的信息消息 |
WARNING | 警告消息 |
LIFECYCLE | 进度信息消息 |
INFO | 信息性消息 |
DEBUG | 调试消息 |
四、Gradle 在 Android Studio 的项目结构分析
在 Android Studio 中新建一个工程(名称为:Gradle),如下所示:
4.1 settings.gradle
settings.gradle 是负责配置 module 的脚本,对应 Settings 类,gradle 构建过程中,会根据 settings.gradle 生成 Settings 的对象。
在 Android Studio 中使用 Ctrl + 鼠标左键点击 include 可以查看 Settings 源码,如下图所示:
也可以到 C:\Users\Administrator.gradle\wrapper\dists\gradle-6.5-all 目录找打Settings的源码,电脑不同可能名称会有所不同。
例如我电脑的路径如下:
C:\Users\Administrator.gradle\wrapper\dists\gradle-6.5-all\2oz4ud9k3tuxjg84bbf55q0tn\gradle-6.5\src\core-api\org\gradle\api\initialization\Settings.java
其中有几个主要的方法如下所示:
-
include(projectPaths)
-
includeFlat(projectNames)
-
project(projectDir)
一般在项目里见到的引用子模块的方法,就是使用 include,这样引用,子模块位于根项目的下一级
include ':app'
如果想指定子模块的位置,可以使用 project 方法获取 Project 对象,设置其 projectDir 参数。
// project(':app') 得到Project对象。
project(':app').projectDir = new File('./app')
4.2 project/build.gradle
project 的 build.gradle 负责整体项目的一些配置,对应的是 Project 类。
gradle 构建的时候,会根据 build.gradle 生成 Project 对象,所以在 build.gradle 里写的 dsl,其实都是 Project 接口的一些方法,Project 其实是一个接口,真正的实现类是 DefaultProject 。
其中几个主要方法有:
-
buildscript // 配置项目的 classpath
-
allprojects // 配置项目及其子模块
-
respositories // 配置仓库地址,后面的依赖都会去这里配置的地址查找
-
dependencies // 配置项目的依赖
以本节所创建的Gradle项目来分析 project/build.gradle,代码如下所示:
buildscript { // 配置脚本的 classpath
repositories { // 项目的仓库地址,会按顺序查找
google()
jcenter()
}
dependencies { // 项目的依赖
classpath "com.android.tools.build:gradle:4.1.1"
}
}
allprojects { // 子模块(module)的配置
repositories {
google()
jcenter()
}
}
task clean(type: Delete) { // clean 任务
delete rootProject.buildDir
}
4.3 module/build.gradle
module 的 build.gradle 是子模块的配置,对应的也是 Project 类。
子项目和根项目的配置是差不多的,不过在子项目里可以看到有一个明显的区别,就是引用了一个插件 plugins { id 'com.android.application'},后面的 android dsl 就是 application 插件的 extension。
其中几个主要方法有:
-
compileSdkVersion // 指定编译需要的 sdk 版本
-
defaultConfig // 指定默认的属性,会运用到所有的 variants 上
-
buildTypes // 一些编译属性可以在这里配置
-
productFlavor // 配置项目的 flavor
以 app 模块的 build.gradle 来分析module/build.gradle,代码如下所示:
// 引入 android gradle 插件
plugins { id 'com.android.application'}
android { // 配置 android gradle 插件需要的内容
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig { // 默认配置
applicationId "com.lx.gradle"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {// 指定编译属性
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {// 指定java的版本
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// flavor 相关配置
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
}
dependencies { // module 所需要的依赖
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
4.4 Gradle Wrapper
gradlew / gradlew.bat
这两个文件用来下载特定版本的 gradle 然后执行的,就不需要开发者在本地再安装 gradle 了。
gradle/wrapper/gradle-wrapper.properties
是一些 gradlewrapper 的配置,其中用的比较多的就是 distributionUrl,可以执行 gradle 的下载地址和版本。
gradle/wrapper/gradle-wrapper.jar
是 gradle wrapper 运行需要的依赖包。
五、Gradle 生命周期
gradle 构建分为三个阶段
1. 初始化阶段
初始化阶段主要做的事情是有哪些项目需要被构建,然后为对应的项目创建 Project 对象。
2. 配置阶段
配置阶段主要做的事情是对上一步创建的项目进行配置,这时候会执行 build.gradle 脚本,并且会生成要执行的 task。
3. 执行阶段
执行阶段主要做的事情就是执行 task,进行主要的构建工作。
gradle 在构建过程中,会提供一些列回调接口,方便在不同的阶段做一些事情,主要的接口有下面几个
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
// 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的
println('Build start')
}
@Override
void settingsEvaluated(Settings settings) {
// Settings 文件解析完成
println('The Settings file is resolved')
}
@Override
void projectsLoaded(Gradle gradle) {
// 项目加载完成
println('Project loading completed')
gradle.rootProject.subprojects.each {
pro ->
pro.beforeEvaluate {
// 项目配置之前调用
println("${pro.name} before")
}
pro.afterEvaluate {
// 项目配置之后调用
println("${pro.name} after")
}
}
}
@Override
void projectsEvaluated(Gradle gradle) {
// 项目解析完成
println('Project analysis completed')
}
@Override
void buildFinished(BuildResult result) {
// 构建完成
println('Build finish')
}
})
gradle.taskGraph.whenReady {
// task 图构建完成
println("task graph build finish")
}
gradle.taskGraph.beforeTask {
// 每个 task 执行前会调这个接口
println("exec task, before")
}
gradle.taskGraph.afterTask {
// 每个 task 执行完成会调这个接口
println("exec task, after")
}
六、自定义插件
gradle 的插件可以看作是一系列 task 的集合。
在 android 工程的 build.gradle 脚本里,开头就是 plugins { id 'com.android.application' },这个就是引入 android gradle 插件,插件里有 android 打包相关的 task。
插件开发可以使用 Groovy 和 Java 语言,这里使用 Java 语言开发,因为 Java语言对于 Android 开发者来说更熟悉。
现在先看看如何实现一个自己的 Plugin
6.1 初始化工程
1)在 android studio 中创建一个 Java Module 工程,名称为 ”plugin-lib“,包名为 "com.lx.plugin",默认创建一个MyPlugin类,如下图所示:
2)在 src/main 目录下创建 resources/META-INF/gradle-plugins 目录,创建 myplugin.properties 文件 (说明:定义的 xxx.properties 文件,而 xxx 就是 apply plugin 时候使用的 id),里面声明插件的入口类,文件里内容如下所示:
# com.lx.plugin.MyPlugin 是自己定义的插件类
implementation-class=com.lx.plugin.MyPlugin
3)修改 plugin-lib 的 build.gradle 文件,代码如下所示:
// 引入 groovy 和 java-library 插件
plugins {
id 'java-library'
id 'groovy'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation gradleApi()
}
到此,plugin-lib 的目录结构如下图所示:
6.2 创建插件
在刚才创建的插件类里,就可以写插件的代码了。插件类继承 Plugin,并实现 apply 接口,apply 就是在 build.gradle 里 apply plugin 'xxx' 的时候要调用的接口了,代码如下所示:
package com.lx.plugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* create by lx
* date 2020/12/4.
* description:自定义插件
*/
public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project target) {
System.out.println("apply my plugin");
}
}
6.3 创建插件的 Task
我们再定义一个 task 类 MyTask,继承自 DefaultTask,代码如下所示:
package com.lx.plugin;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
/**
* create by lx
* date 2020/12/4.
* description:task任务
*/
public class MyTask extends DefaultTask {
@TaskAction
void action() {
System.out.println("my task exec");
}
}
接下来在 plugin 中注册这个 Task
public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project target) {
System.out.println("apply my plugin");
// 注册 MyTask 任务
target.getTasks().create("myTask", MyTask.class);
}
}
6.4 安装本地插件
我们首先需要在 plugin-lib 的 build.gradle 中引入 maven 插件,并且配置 install 相关的属性,代码如下所示:
plugins {
...
id 'maven'
}
install {
repositories.mavenInstaller {
pom.version = '1.0.1' // 配置插件版本号
pom.artifactId = 'myplugin' // 配置插件标识
pom.groupId = 'com.lx.plugin' // 配置插件组织
}
}
...
之后执行 gradlew install 便会把插件安装在本地 maven 仓库。
然后需要使用插件的地方引入插件的 classpath ,例如在 Gradle 的 build.gradle 中使用插件,添加如下代码:
buildscript { // 配置脚本的 classpath
repositories { // 项目的仓库地址,会按顺序查找
google()
jcenter()
mavenLocal() // 本地maven仓库
}
dependencies { // 项目的依赖
classpath "com.android.tools.build:gradle:4.1.1"
// 添加插件
classpath 'com.lx.plugin:myplugin:1.0.1'
}
}
allprojects { // 子模块(module)的配置
repositories {
google()
jcenter()
mavenLocal()
}
}
task clean(type: Delete) { // clean 任务
delete rootProject.buildDir
}
最后在 app 的 build.gradle 中加载插件,代码如下所示:
plugins {
id 'com.android.application'
// 添加自定义插件
id 'myplugin'
}
然后 “Sync Now” 同步一下就可以在 Gradle -> app -> Tasks -> other 中看到 myTask 任务了。
点击上图中的 myTask 运行此任务,结果如下图所示:
说明:在Android Studio 中开发插件调试方法与开发普通项目一样,可以使用断点调试,如下图所示: