1 Groovy & Gradle基础
1.1 Groovy
这里不作介绍,像写脚本一样写Java,也是运行在JVM上。比较简单,是Gradle基础,当然,也可以用纯Java来写gradle。
1.2 Gradle
Gradle是一个工具,同时它也是一个编程框架。使用这个工具可以完成app的编译打包等工作,当然也可以用它干其他的事情。Gradle相关文档见这里,我们编写所谓的编译脚本,其实就是在用Gradle提供的API。
Gradle是一个框架,它提供了很多基本组件:Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的task。比如一个Android APK的编译可能包含:Java源码编译task、资源编译task、JNI编译task、lint检查task、打包生成APK的task、签名task等。每个Project具体包含多少个task是有编译脚本指定的插件决定的,这个在后面会讲到。
Gradle作为一个框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件等等。
1.2.1 Gradle对象
Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对象:
(1) Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
(2) Project对象:每一个build.gradle会转换成一个Project对象。
(3) Settings对象:显然,每一个settings.gradle都会转换成一个Settings对象。
其中最重要的就是project对象,project包含若干个tasks,在project中,我们要做如下工作:
- 加载插件
- 配置插件
- 设置属性
加载插件
加载插件是调用它的apply函数,我们常见的就有以下几个:
配置插件
配置插件这里就不作多说,后文会详细讲到。
设置属性
如果是单个脚本,则不需要考虑属性的跨脚本传播,但是Gradle往往包含不止一个build.gradle文件,比如我设置的utils.gradle,settings.gradle。如何在多个脚本中设置属性呢?
Gradle提供了一种名为extra property的方法。extra property是额外属性的意思,在第一次定义该属性的时候需要通过ext前缀来标示它是一个额外的属性。定义好之后,后面的存取就不需要ext前缀了。ext属性支持Project和Gradle对象。即Project和Gradle对象都可以设置ext属性。后文会讲到。
2 Android Gradle简介
目前Android基本上都是采用Gradle进行构建,针对大家对gradle用法的一些疑惑,下面将详细剖析gradle在android构建中的用法。
2.1 Android Gradle目录结构
首先来看看一个典型的Android工程中gradle的结构:
上图基本就是目前典型的Android结构(Dolphin里面的更为复杂,相对Gradle用法有进一步了解的,请参考Dolphin),万变不离其宗,理解了这些,再复杂的配置都能清晰看懂。
2.2 Top Level Gradle
Top Level Gradle用于配置所有Module的属性,包括一些公共的方法、公共的依赖项。一般包含3个文件,build.gradle、settings.gradle、gradle.properties。下面来依次了解下每个文件是用来干什么的。
2.2.1 build.gradle文件
- 代码示例12345678910111213141516171819202122232425262728293031323334353637383940414243444546// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {repositories {jcenter()}dependencies {classpath 'com.android.tools.build:gradle:1.2.3'// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}}allprojects {repositories {jcenter()}}task clean(type: Delete) {delete rootProject.buildDir}/*** 定义外部属性,所有Module中均用这里的配置,避免Project修改时所有Module都要更改.*/ext {// Android configandroidBuildToolsVersion = '23.0.2'androidMinSdkVersion = 16androidTargetSdkVersion = 23androidCompileSdkVersion = 23androidVersionCode = 1androidVersionName = '1.0'// libraries configsupportVersion = '23.1.1'picasso = '2.5.2'commonDependencies = [appcompatV7 : "com.android.support:appcompat-v7:${supportVersion}",design : "com.android.support:design:${supportVersion}",picasso : "com.squareup.picasso:picasso:${picasso}"]}
接下来来具体看看top level构建脚本下每个节点代表什么。
buildscript节点
buildscript节点是运行该构建脚本所需要用到的依赖文件,repositories则指定了依赖文件的查找位置。一般而言,这个script block都写在脚本开头,声明这个脚本本身运行所需的依赖。allprojects
设置当前project以及所有sub project中依赖文件的查找路径。task clean
定义一个新的task叫做clean,类型为Delete。Android Plugin内置了clean方法,但是该方法在module中,module中内置的clean方法只会清理module中的文件并删除module中的build目录,但是工程根目录中的build文件没有被删除。因此,这里定义的clean方法删除根目录下的build文件夹。
2.2.2 settings.gradle文件
示例
1include ':app', ':third_party'说明
管理所有sub project,凡是要被编译的子项目,都要写在这里,gradle按照这个配置递归编译子项目。
2.2.3 gradle.properties
配置gradle JVM运行的一些参数,MaxHeapSize之类的,本地环境配置。
2.3 Module Level Gradle
模块级的脚本描述的是该模块的编译过程,一般而言,Android用到的有两种:
Application:apply plugin: ‘com.android.application’,编译结果为apk
Library:apply plugin: ‘com.android.library’,编译结果为aar
application插件和library插件内容配置差不多,这里分别用讲述以2中形式来分别配置Module的gradle.
2.3.1 application build.gradle代码示例
首先来看第一种方式,直接读取top level的build.gradle中属性值,然后再使用:
2.3.2 library build.gradle代码示例
第二种读取方式则更为简单,直接导入top level的build.gradle,随后可以直接读取其中的属性值:
推荐使用这种方式来读取ext值。
3 Gradle之task详解
以上还只是了解gradle的基本配置,而gradle构建最最核心的部分是各个插件提供的task,每个task独自执行自己的任务,将构建一步步拆分。
3.1 task简介
task是gradle中的一种数据结构,它代表一些要被执行的工作,是构建任务的最基本执行单元,不同的插件有不同的task,每一个task都需要和一个project相关联。
可以看到,上面属于library插件的third_party的task和属于application插件的app的task是不相同的。
需要强调的一点是,org.gradle.api.Project这个类是build.gradle构建脚本同gradle交互的主要接口,几乎所有方法都在这个类中能找得到。
3.2 自定义task
Project提供了task函数方便我们创建task,先来看看Project中是如何定义task的:
简要描述就是,一旦task被创建,那么它会被当做project的一个属性,由gradle自动执行,Project有几个关于task的重载版本,功能大致一样。我们在Android中一般看的task是形如下面的格式:
“<<”符号表示的task.doLast,”>>”表示task.doFirst,后面的闭包表示task执行完之后/之前,再执行这段闭包。
type:someType又是什么意思呢?其实就是告诉gradle,这个新建的task是从哪个基类task派生而来的。Gradle本身提供了一些通用的task,比如Copy,Delete等等。
以一个例子来看看task的创建:
这个例子在Top level的build.gradle中创建一个名为clean,type为delete的task,先来看看top level中tasks有什么变化:
可以看到,task一旦创建即被加入到project的task序列中,成为project的一个属性。
这个task具体做什么呢?task里面的这段闭包已经说明了,就是删除根目录下的build文件夹。task里面可以做很多事情,具体下面会介绍到。
3.3 task扩展
本小节将在新建task基础上做些扩展,来展示task是如何做更多事情的,相信大家看过之后,不再畏惧gradle,即使不会写但也至少能看得懂。
Android构建给我们已经提供了很多task(可以参考这里),这里结合Android中multidex实现来讲讲task的更多用法:
上述代码功能大概意思就是江create{Debug/Release}MainDexClassList这个task(对这个不熟的可以参考这里)形成的maindexkeep文件给替换掉。下面来具体分析这个例子:
(1) 首先这个在top level的build.gradle中,因为是要干预整个工程的分包,而不是针对某个module的;
(2) 这里的意思是将所有的task添加到project中,闭包有一个输入参数:task,这里”task ->”相当于迭代器,闭包内有一个默认参数it,代表的就是tasks,这里没有用到。
(3) 获取指定的task,可以根据getByName方法获取指定的task。
除了上面提到的tasks.whenTaskAdded之外,我们还常见到project.afterEvaluated,它的意思是gradke解析完所有的task之后,并且在具体task开始执行之前,我们可以做的事情。
Android每个project都提供了tasks、project、gradle等内置对象供我们来访问,利用这些对象我们就可以对每个task进行hook,进而能够充分干预整个构建过程。