本文主要介绍 Gradle 的一些基础知识与原理。
本文主要包括以下内容:
Gradle 的前世今生
前世
1. 构建工具的进化:Ant, Maven, Gradle
今生
1. Gradle 到底是什么?
2. Gradle Wrapper 是什么?
3. AGP 到底是什么?
4. gradle.properties 是什么?
5. settings.gradle 是什么?
6. build.gradle 是什么?
7. Gradle 生命周期是怎样的?
8. build.gradle 配置详解。
首先来回答一个问题吧:什么是 Gradle 呢?我理解的话,就是一 “构建(build)” 项目的工具,常见的使用场景的呢,就是咱们编写的 Java 代码、C++ 代码和资源文件经过编译、链接等操作,最终打包成一个 apk。
下面咱们先来了解一下,什么是构建工具,它又是怎么衍化的。
一、构建工具的进化:ant, maven, gradle
我们要写一个 Java 程序,一般的步骤也就是编译,测试,打包。这个构建的过程,如果文件比较少,我们可以手动使用 java, javac, jar 命令去做这些事情。但当工程越来越大,文件越来越多,这个事情就不是那么地令人开心了。因为这些命令往往都是很机械的操作。但是我们可以把机械的东西交给机器去做。
这时,构建工具就应运而生了。
1. Apache Ant
在 linux 上,有一个工具叫 make。我们可以通过编写 Makefile 来执行工程的构建。windows 上相应的工具是 nmake。这个工具写起来比较罗嗦,所以从早期,Java 的构建就没有选择它,而是 Apach 基金组织新建的一个叫做 Ant 的构建项目工具。Ant 的思想和 makefile 比较像。定义一个任务,规定它的依赖,然后就可以通过 Ant 来执行这个任务了。我们通过例子看一下。下面列出一个 Ant 工具所使用的 build.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<project name="HelloWorld" default="run" basedir=".">
<property name="src" value="src"/>
<property name="dest" value="classes"/>
<property name="jarfile" value="hello.jar"/>
<target name="init">
<mkdir dir="${dest}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${dest}"/>
</target>
<target name="build" depends="compile">
<jar jarfile="${jarfile}" basedir="${dest}"/>
</target>
<target name="test" depends="build">
<java classname="test.ant.HelloWorld" classpath="${hello_jar}"/>
</target>
<target name="clean">
<delete dir="${dest}" />
<delete file="${hello_jar}" />
</target>
</project>
可以看到 Ant 的构建脚本还是比较清楚的。Ant 定义了五个任务:init, compile, build, test, clean,每个任务做什么都定义清楚了。打包之前要先编译,所以通过 depends 来指定依赖的路径。如果在命令行里执行 ant build,那就会先执行compile,而 compile 又依赖于 init,所以就会先执行 init。看起来很合理,对吧?有了这个东西以后,我们只要一条命令:
ant test
就可以执行编程,打包,测试了。为开发者带来了很大的便利。
其实在 Android Studio 出来之前,我们在 eclipse 中也有构建项目的概念,当时用的就是 Ant,google 给我们集成到 SDK 中了,我们当初之所以能把一大堆 Java 代码和图片打包成一个 apk 主要靠它。
但是 Ant 有一个很致命的缺陷,那就是没办法管理依赖。我们一个工程,要使用很多第三方工具,不同的工具,不同的版本。每次打包都要自己手动去把正确的版本拷到 lib 下面去,不用说,这个工作既枯燥还特别容易出错。为了解决这个问题,我们来看 Maven 如何完成同样的工作。
2. Apache Maven
Maven 最核心的改进就在于提出仓库这个概念,它抛弃了 Ant 中通过 target 定义任务的做法。我们可以把所有依赖的包,都放到仓库里去,在我们的工程管理文件里,标明我们需要什么什么包,什么什么版本。在构建的时候,Maven 就自动帮我们把这些包打到我的包里来了。我们再也不用操心着自己去管理几十上百个 jar 文件了。
Maven 提出,要给每个包都标上坐标,这样,便于在仓库里进行查找。所以,使用 Maven 构建和发布的包都会按照这个约定定义自己的坐标。例如:
pom.xml
[html] view
plaincopy
<project xmlns=“http://maven.apache.org/POM/4.0.0”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>com.technologyconversations</groupId>
<artifactId>java-build-tools</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
</plugin>
</plugins>
</build>
</project>
上面,定义了用到包的坐标是 junit:junit:4.11,而我的工程要依赖 junit:junit:4.10。那么 maven 就会自动去帮我把 junit 打包进来。如果我本地没有 junit,maven 还会帮我去网上下载。下载的地方就是远程仓库,我们可以通过 repository 标签来指定远程仓库。
通过执行下面的命令来运行 maven goal 生成 jar 文件。
mvn package
主要的区别在于 Maven 不需要指定执行的操作。没有创建 task,而是设置了一些参数(有哪些依赖,用哪些插件…)。Ant 和 Maven 的 xml 文件都会随时间而变大,为了说明这一点,我们加入 CheckStyle,FindBugs 和 PMD 插件来进行静态检查,三者是 Java 项目中使用很普遍的的工具。我们希望将所有静态检查的执行以及单元测试一起作为一个单独的目标验证。当然我们还应该指定自定义的 checkstyle 配置文件的路径并且确保错误时能够提示。更新后的 Maven 代码如下:
pom.xml
[plain] view
plaincopy
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.12.1</version>
<executions>
<execution>
<configuration>
<configLocation>config/checkstyle/checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.4</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
通过执行下面的命令来运行 maven goal,包括单元测试,静态检查,如 CheckStyle,FindBugs 和 PMD。
mvn verify
我们需要写很多 xml 来进行基本的常用的任务。在实际项目中有更多的依赖和 task,Maven 的 pom.xml 很容易就有成百上千行的配置。
既然有缺点,那肯定也有优点,Maven 的主要优点是引入了生命周期的概念。这个问题我们暂时先放一下。
Maven 已经很好了,可以满足绝大多数工程的构建。那为什么我们还需要新的构建工具呢?依赖管理不能很好地处理相同库文件不同版本之间的冲突;xml 作为配置文件的格式有严格的结构层次和标准,定制化目标很困难;因为 Maven 主要聚焦于依赖管理,实际上用 Maven 很难写出复杂、定制化的构建脚本,甚至不如 Ant ;用xml 写的配置文件会变得越来越大,越来越笨重。在大型项目中,它经常什么“特别的”事还没干就有几百行代码。基于这些原因,我们来看今天的主要内容 Gradle。
3. Gradle Build Tool
随着时间的推移,开发人员对拥有和使用 DSL(Domain Specific Languages ) 越来越感兴趣——简单地说,这将允许他们使用为特定领域定制的语言来解决特定领域中的问题。
Gradle 采用了这种方法,它使用的是基于 Groovy 或 Kotlin 的 DSL。这导致了更小的配置文件和更少的混乱,因为该语言是专门为解决特定领域的问题而设计的。按照惯例,Gradle 的配置文件在 Groovy 中称为 build.Gradle,在Kotlin 中称为 build.Gradle.kts 。注意,Kotlin 在自动完成和错误检测方面提供了比 Groovy 更好的IDE支持。
而且,Gradle 并不是另起炉灶,它基于 Groovy 语言,并充分地使用了 Maven 的现有资源。继承了 Maven 中仓库,坐标,依赖这些核心概念。文件的布局也和 Maven 相同。但同时,它又继承了 Ant 中 target 的概念,我们又可以重新定义自己的任务了(gradle 中叫做 task)。
简单点,Gradle 是一个依赖管理和构建自动化工具。
我们来体验一下,新建一个空目录,在命令行,执行
gradle init --type java-library
可以看到新创建了一个工程,工程根目录下,主要看这几项:
build.gradle gradle settings.gradle src
我们看一下,build.gradle 的内容:
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java Library project to get you started.
* For more details take a look at the Java Libraries chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.5/userguide/java_library_plugin.html
*/
plugins {
// Apply the java-library plugin to add support for Java Library
id 'java-library'
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:29.0-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.13'
}
内容很简单,引入了 java 插件,指定仓库,指定依赖。可以看到依赖的设定相比起 xml 的写法,变得大大简化了。
使用 Gradle,任务又变成了核心概念了。我们就来体验一下任务。
在 build.gradle 里添加这样的任务:
task hello {
println 'welcome to gradle';
}
然后在命令行执行
gradle -q hello
注:-q 参数。用来控制 Gradle 的日志级别,可以保证只输出我们需要的内容。
就可以看见打印一行 "welcome to gradle"。在使用 maven 构建的时候,如果想临时对某一个构建任务加一点 log,会是个非常困难的事情 。但在 Gradle 里,就变得非常简单,因为 gradle 的背后其实是 groovy 这个编程语言在起作用。为了验证这一点,我们再改一下:
task hello {
3.times {
println 'welcome to gradle';
}
}
然后执行 gradle -q hello,就可以看到连续打印了三行。使用脚本语言进行构建,这几乎给了我们任何的能力,我们可以在构建的时候做任何的事情,甚至你可以直接让 Gradle 帮你做表达式求值。
接下来,我们来认识一下 Gradle。