Gradle构建基本原理

1,787 阅读7分钟

Gradle编译周期

本文的参考地址:blog.csdn.net/zsjlovesm52…

在解析 Gradle 的编译过程之前我们需要理解在 Gradle 中非常重要的两个对象。Project和Task。

每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,task 里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。

在编译过程中, Gradle 会根据 build 相关文件,聚合所有的project和task,执行task 中的 action。因为 build.gradle文件中的task非常多,先执行哪个后执行那个需要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task 都需要依赖其他 task 来执行,没有被依赖的task 会首先被执行。所以到最后所有的 Task 会构成一个 有向无环图(DAG Directed Acyclic Graph)的数据结构。

编译过程分为三个阶段:

初始化阶段:创建 Project 对象,如果有多个build.gradle,也会创建多个project.

配置阶段:在这个阶段,会执行所有的编译脚本,同时还会创建project的所有的task,为后一个阶段做准备。

执行阶段:在这个阶段,gradle 会根据传入的参数决定如何执行这些task,真正action的执行代码就在这里.

Gradle Files

对于一个gradle 项目,最基础的文件配置如下:

一个项目有一个setting.gradle、包括一个顶层的 build.gradle文件、每个Module 都有自己的一个build.gradle文件。 setting.gradle:这个 setting 文件定义了哪些module 应该被加入到编译过程,对于单个module 的项目可以不用需要这个文件,但是对于 multimodule 的项目我们就需要这个文件,否则gradle 不知道要加载哪些项目。这个文件的代码在初始化阶段就会被执行。 顶层的build.gradle:顶层的build.gradle文件的配置最终会被应用到所有项目中。它典型的配置如下: buildscript:定义了 Android 编译工具的类路径。repositories中,jCenter是一个著名的 Maven 仓库。 allprojects:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。

每个项目单独的 build.gradle:针对每个moudle 的配置,如果这里的定义的选项和顶层build.gradle定义的相同,后者会被覆盖。典型的 配置内容如下:

gradle wrapper

Gradle 不断的在发展,新的版本难免会对以往的项目有一些向后兼容性的问题,这个时候,gradle wrapper就应运而生了。

gradlw wrapper 包含一些脚本文件和针对不同系统下面的运行文件。wrapper 有版本区分,但是并不需要你手动去下载,当你运行脚本的时候,如果本地没有会自动下载对应版本文件。

在不同操作系统下面执行的脚本不同,在 Mac 系统下执行./gradlew ...,在windows 下执行gradle.bat进行编译。

如果你是直接从eclipse 中的项目转换过来的,程序并不会自动创建wrapper脚本,我们需要手动创建。在命令行输入以下命令即可

gradle wrapper --gradle-version 2.4

它会创建如下目录结构:

wrapper 就是我们使用命令行编译的开始。下面我们看看 wrapper 有什么样的作用。

Gradle basics Gradle 会根据build 文件的配置生成不同的task,我们可以直接单独执行每一个task。通过./gradlew tasks列出所有task。如果通过同时还想列出每个task 对应依赖的其他task,可以使用./gradlew tasks -all。

其实每当我们在Android Studio点击 build,rebuild,clean菜单的时候,执行的就是一些gradle task.

Android tasks

有四个基本的 task, Android 继承他们分别进行了自己的实现:

assemble:对所有的 buildType 生成 apk 包。

clean:移除所有的编译输出文件,比如apk

check:执行lint检测编译。

build:同时执行assemble和check命令

这些都是基本的命令,在实际项目中会根据不同的配置,会对这些task 设置不同的依赖。比如 默认的 assmeble 会依赖 assembleDebug 和assembleRelease,如果直接执行assmeble,最后会编译debug,和release 的所有版本出来。如果我们只需要编译debug 版本,我们可以运行assembleDebug。除此之外还有一些常用的新增的其他命令,比如 install命令,会将编译后的apk 安装到连接的设备。我们运行的许多命令除了会输出到命令行,还会在build文件夹下生产一份运行报告。比如check命令会生成lint-results.html.在build/outputs中。

BuildConfig

这个类相信大家都不会陌生,我们最常用的用法就是通过BuildConfig.DEBUG来判断当前的版本是否是debug版本,如果是就会输出一些只有在 debug 环境下才会执行的操作。 这个类就是由gradle 根据 配置文件生成的。为什么gradle 可以直接生成一个Java 字节码类,这就得益于我们的 gradle 的编写语言是Groovy, Groovy 是一种 JVM 语言,JVM 语言的特征就是,虽然编写的语法不一样,但是他们最终都会编程 JVM 字节码文件。同是JVM 语言的还有 Scala,Kotlin 等等。这个功能非常强大,我们可以通过在这里设置一些key-value对,这些key-value 对在不同编译类型的 apk 下的值不同,比如我们可以为debug 和release 两种环境定义不同的服务器。比如:

Repositories

 Repositories 就是代码仓库,这个相信大家都知道,我们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。在编译的执行阶段,gradle 将会从仓库中取出对应需要的依赖文件,当然,gradle 本地也会有自己的缓存,不会每次都去取这些依赖。gradle 支持多种 Maven 仓库,一般我们就是用共有的jCenter就可以了。有一些项目,可能是一些公司私有的仓库中的,这时候我们需要手动加入仓库连接:

如果仓库有密码,也可以同时传入用户名和密码: 我们也可以使用相对路径配置本地仓库,我们可以通过配置项目中存在的静态文件夹作为本地仓库:

Dependencies

我们在引用库的时候,每个库名称包含三个元素:组名:库名称:版本号,如下:

Local dependencies

File dependencies

通过files()方法可以添加文件依赖,如果有很多jar文件,我们也可以通过fileTree()方法添加一个文件夹,除此之外,我们还可以通过通配符的方式添加,如下:

Native libraries

配置本地 .so库。在配置文件中做如下配置,然后在对应位置建立文件夹,加入对应平台的.so文件。

文件结构如下:

Library projects

如果我们要写一个library项目让其他的项目引用,我们的bubild.gradle的plugin 就不能是andrid plugin了,需要引用如下plugin

apply plugin: 'com.android.library'

引用的时候在setting文件中include即可。

如果我们不方便直接引用项目,需要通过文件的形式引用,我们也可以将项目打包成aar文件,注意,这种情况下,我们在项目下面新建arrs文件夹,并在build.gradle 文件中配置 仓库:

当需要引用里面的某个项目时,通过如下方式引用:

Build Variants

在开发中我们可能会有这样的需求:

我们需要在debug 和 release 两种情况下配置不同的服务器地址;

当打市场渠道包的时候,我们可能需要打免费版、收费版,或者内部版、外部版的程序。

渠道首发包通常需要要求在欢迎页添加渠道的logo。等等

为了让市场版和debug版同时存在与一个手机,我们需要编译的时候自动给debug版本不一样的包名。

这些需求都需要在编译的时候动态根据当前的编译类型输出不同样式的apk文件。这时候就是我们的buildType大展身手的时候了。