Android Gradle 使用
一.Gradle
(一)简介
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具,是一个基于JVM的构建工具,支持maven, Ivy仓库,支持传递性依赖管理,而不需要配置文件,其脚本的编写是一种基于Groovy的特定领域语言(DSL)。
(二)项目结构
通过上图AndroidStudio为我们自动创建的项目结构,可以发现,一个项目中可以有多个的子项目的存在,那么对于这种情况gradle是如何进行项目构建的呢:
-
蓝色:我们的根项目是KotlinApplication,其下有两个子项目,分别说主项目app,和子项目subproject1
-
红色:红色的三个build.gradle,是项目自动生成的,app和subproject1目录下的build.gradle文件,就是构建相应项目的脚本文件,也就是说每个项目有自己的构建脚本;而KotlinApplication目录下的build.gradle文件,是针对于整个项目的构建脚本,他用于对整个项目的构建做一些操作
-
黄色:黄色的settings.gradle,位于根项目目录下,是用来配置有哪几个项目的脚本文件
//settings.gradle include ':app', ':subproject1'
代表项目中有app、subproject1两个子项目需要进行构建,配置后,相应项目的build.gradle脚本文件在构建时就会被执行
-
绿色:绿色的两个文件gradlew和gradlew.bat,是用来执行gradle命令的,gradlew即gradle wrapper缩写,用于兼容不同版本的gradle所提供的命令行脚本文件,而bat则是用在win系统上的脚本文件,所以我们在使用gradle命令时,通常都是./gradlew xxx的形式,执行的就是该文件对应的命令
除此之外,build.gradle文件执行的project就是包含其的目录,该目录下的内容对应的就是一个Project对象,而且gradle还提供了构件时的临时文件夹,用于存放构建产生的文件,位置就在其对应project目录下的build文件夹:比如执行app下的build.gradle,其project对应的就是app,而app的build文件夹就在app/目录下
以上就是gradle为构建项目提供的目录结构
(三)基本概念
-
Project:每个gradle脚本会将构建的项目,抽象为一个Project对象,包含有项目相关的一些信息和操作
-
Task:gradle认为每个project的构建过程,其实是由一个一个的任务完成,比如依赖的引入、资源的打包等等,而将每一个任务抽象为一个Task对象
-
Plugin:Plugin为一个插件对象,用于完成一个模块的功能,每个Plugin可以对project进行整体的操控,比如引入依赖、执行task等等,是对一组Task的封装
-
Dependencies:每个project都可以引入第三方的一些库(功能),称为依赖,包括不同网站上的、本地的项目等等,gradle将其抽象为Dependencies对象
(四)脚本执行过程
当我们使用gradlew命令执行一个task时,gradle会先通过settings.gradle文件中指定的项目,创建处相应的Project对象,然后依次执行其build.gradle文件,执行build.gradle文件,其实会分为两个阶段:
1.配置阶段
对于一个gradle脚本文件,会依次扫描其语句,然后执行配置期的语句,比如:创建Task/Plugin对象、调用其初始化方法、执行Task的定义期语句、执行configure方法进行自定义配置等,而真正的执行内容不会再该阶段执行
2.执行阶段
当配置阶段完成后,会按照定义,执行相应Task的执行方法完成工作,不依赖于脚本的定义顺序
以上这些执行过程,在下面的讲解中再具体说明
二.Task
(一)创建
对于创建Task的方式,先来看一中最常用的方式:
task HelloWorld << { println 'Hello World!' }
该Task在执行时会输出Hello World!
其实,是gradle脚本使用的基于groovy的DSL语言帮助我们如此快捷的实现一个Task的定义,其实质上是“啰嗦”的方法调用的简写:
project.tasks.create('HelloWorld').doLast(new Closure() {...})
由代码可知,其本质上其实就是调用了当前project对象的TaskContainer(一个Project包含的所有Task的对象)的create方法,传入一个字符串作为Task的name,然后调用doLast()方法,传入一个闭包对象用于设置task的执行代码,但是通过DSL语言我们就可以非常简单的方式书写了(因为闭包有代理对象,可以不用声明调用者;再加上groovy调用方法时可以再方法名后直接跟参数),所以一下几种方式创建Task是一样的(但是写法上逐步简化):
project.tasks.create('HelloWorld').doLast {
println 'Hello World!'
}
project.task('HelloWorld').doLast {
println 'Hello World!'
}
task HelloWorld << {
println 'Hello World!'
}
(二)定义执行代码
上面是定义Task对象,在扫描阶段,会创建相应的Task类的对象,那么如何定义其要执行的代码呢?gradle提供了几种常用的方式向Task插入代码
1.配置阶段代码
在定义Task时直接定义的语句,会被认为是配置阶段的代码,在扫描gradle文件构建Task对象后会立刻执行
task HelloWorld {
println 'Hello World!'
}
如上代码,在执行该Task时,在扫描脚本文件阶段就会执行,输出Hello World!
2.doFirst
为task定义doFirst执行体,在运行阶段执行task时,会先执行该段代码
task HelloWorld {
doFirst {
println 'task run first'
}
}
如上代码,在运行阶段执行该task时,会输出task run first
3.doLast
对应doFirst,也有doLast方法,我们在定义阶段的<<符号为doLast的简写方式,即在运行阶段执行task时,会最后执行该段代码,为了验证这几几种方法的执行顺序,我们来举一个例子
task HelloWorld {
doLast {
println 'task run last'
}
doFirst {
println 'task run first'
}
println 'Hello World!'
}
执行命令./gradlew HelloWorld,输出结果为
Hello World!
//执行task
:HelloWorld
task run first
task run last
即可验证,定义阶段的代码会在配置期就执行,运行阶段时,doFirst先执行,doLast后执行
(三)配置
task有了执行代码还不够,因为我们有时需要开放出去一些选项完成某项功能,使功能动态,对此,gradle为task甚至project等对象,都提供了properties属性供对象设置和使用,相应的有configure()方法进行一些配置
1.property
gradle为project、task等对象,都提供了一些默认属性,比如description,以及上面说过的project的tasks等等,我们可以直接设置这些属性的值,如:
task HelloWorld << {
println '$description'
}
Test.description = 'I am HelloWorld'
除去这些基本属性,我们也可以为其定义自己的property:
task HelloWorld {
def custom = 'i am custom'
doLast {
println custom
}
}
2.configure
除了直接调用属性去赋值外,gradle还提供了一种方式,即为task增加configure方法,对其进行配置:
task HelloWorld << {
println description
}
HelloWorld.configure {
description = 'configure1'
}
HelloWorld {
description = 'configure2'
}
两种方式一样,本质都是调用了HelloWorld这个Task对象的configure方法,传入闭包,更新自己的description属性,该部分是在配置阶段就被执行的,所以在运行阶段执行task时,拿到的就是最后一次配置的description
3.Project的配置
说起配置,不得不说的就是关于project的配置,gradle为每个构建项目生成一个Project对象,并且project提供了一些方法,用于统一配置所有项目,而不用每个项目配置一遍:
//1.全部project(包括rootProject)的配置
allprojects {
//为所有项目添加一个仓库地址
repositories {
jcenter()
}
}
//2.全部子project(不包括rootProject)的配置
subprojects {
//为每一个子project添加一个task
task showProjectName << {
//打印其name,project指的就是拥有当前task的project领域对象
println project.name
}
}
//3.调用project的configure方法,进行自定义的配置(单个对象/多个对象统一进行配置)
configure(allprojects.findAll { it != rootProject }) {//找到所有子project
task showSubProjectName << {
println project.name
}
}
另外,对于project的属性,也有相应的方式进行定义:
//直接改变Project的property
description 'this is project'
version 'this is project\'s version'
task showProjectProperty << {
println version
//delegate为Task,Task有description,所以要明确调用project的该属性
println project.description
}
//添加Project的property
ext.p1 = 'property1'
ext {
p2 = 'property2'
}
task addProjectProperty << {
//使用时不用ext.xxx
println p1
println p2
//通过命令行加入-Pp3='xxx'来实现
println p3
}
-
直接改变其已有属性值,因为build.gradle的领域对象就是当前的project对象,所以直接调用属性就会从project中去查找
-
ext.xxx来增加一个属性
-
ext {},通过闭包来配置,类似于task的configure
-
在执行task命令时,加入-Pxx = 'xxx’命令行参数,脚本里直接使用即可
(四)Task依赖关系
Task之间也可以有依赖关系,从而影响task的执行顺序,其实现也非常简单,有几种方式:
//app
task app_test_task1 << {
printName('app_test_task1')
}
task app_test_task2(dependsOn: 'app_test_task3') << {
printName('app_test_task2')
}
task app_test_task3 << {
printName('app_test_task3')
}
//subproject1
task subproject1_task1 << {
printName('subproject1_task1')
}
app_test_task1.dependsOn app_test_task2, ':subproject1:subproject1_task1'
-
dependsOn为Task的一个方法,可以传入一组task作为被依赖项,传入的可以是直接定义的task,也可以是task名字字符串
-
被依赖的task可以是其他项目的task,只要传入正确的task路径名字就可以
-
在定义task时,可以直接像app_test_task2一样,使用一个带参数的重载方式进行定义,并传入参数,代表dependsOn是app_test_task3
在声明了依赖关系后,执行某一个task,会先执行其依赖的task,最后执行其自身,如执行./gradlew app_test_task1,结果如下:
:app:app_test_task3
app_test_task3
:app:app_test_task2
app_test_task2
:subproject1:subproject1_task1
subproject1_task1
:app:app_test_task1
app_test_task1
(五)自定义Task
以上是介绍task的基本创建方式,创建的对象其实都是DefaultTask类的实例,gradle还为我们提供了其他的一些类型,以便于完成一些常用功能,甚至我们也可以自定义Task类型
1.Task的类型
task的类型,我们可以在声明时候指定,比如:
task clean(type: Delete) {
delete rootProject.buildDir
}
task copy(type: Copy) {
from '../source'
into '../destination'
}
如上代码,此时构建的task类型就是Delete和Copy类型的了,不同类型的task支持的property不一样,功能也不一样,于是我们就可以通过指定type,来快速的完成删除和复制的功能了!
2.自定义Task
我们也可以自己用groovy来定义Task,就像写java一样的简单:
class MessageTask extends DefaultTask {
//可配置property
@Optional
String message = 'default message'
//执行的方法
@TaskAction
def outputMessage() {
println "message is ${message}"
}
}
task msg1(type: MessageTask)
task msg2(type: MessageTask) {
message = 'i am msg2'
}
-
通过@Optional标注可以被配置的property
-
通过@TaskAction来标注task的执行方法,在运行阶段,会执行该方法
-
定义task时,type就可以指定为我们自己写的Task类上,并且在configure()里,可以去动态设置property
运行./gradlew msg1和./gradlew msg2,结果如下:
:msg1
message is default message
:msg2
message is default i am msg2
三.Plugin
plugin插件,上面说过,是用来管理Project的,通常我们会写一个插件,向Project中加入不同的task并进行管理,来实现某个功能模块,下面来看看如何定义Plugin
我们直接来看一个简单的完整例子,然后按照例子来说明:
//定义Extension对象,定义可配置参数
class MyPluginExtension {
String value = 'default value'
MyPluginExtension() {
}
}
//定义Plugin
class MyPlugin implements Plugin<Project> {
MyPlugin() {
}
//执行方法
@Override
void apply(Project target) {
//定义Extension对象,动态获取可配置参数
target.extensions.create('MyPlugin', MyPluginExtension)
//定义了一个task
target.task('MyTask') {
doLast {
println "value is ${target.getExtensions().findByType(MyPluginExtension).value}"
}
}
//project的build.gradle文件执行完毕的回调
target.afterEvaluate {
println 'project\'s build.gradle done'
}
}
}
//应用plugin
apply plugin: MyPlugin
//配置可选参数
MyPlugin {
value = 'heheda'
}
(一)定义Extension
作为plugin,一般都会有可配置参数供外部选择,这样就可以做到功能的动态化,定义extension对象,就像java定义一个model类一样简单,只需声明出其参数即可,这里只有一个value,默认值为’default value’
Project实现了ExtensionAware,其getExtensions()方法,即project.extensions返回了ExtensionContainer对象,其create方法就是将具体的Extension对象与name对应起来,通过’MyPlugin’对应到MyPlugin这个Extension对象
(二)定义Plugin
-
实现Plugin接口,重写其apply方法,完成逻辑;该方法就是Plugin对象运行的方法,参数为当前的project对象,是与gradle交互的入口
-
project对象的extensions维护着project的所有extension对象,调用create方法,创建一个MyPluginExtension类型的extension对象
-
定义一个Task,在执行时,可以通过project的extension将我们传入的MyPluginExtension取出,并获取其中的value值输出,因为是在运行阶段,所以value值是配置后的
-
project的afterEvaluate方法,是一个用于监听gradle文件执行完毕的方法
(三)应用Plugin
-
Project实现了PluginAware接口,其getPlugins()方法返回PluginsContainer对象,project通过调用其重写的apply方法,可以指定应用某个plugin
注:apply不光可以应用插件,也可以应用其他gradle文件,如:apply from: file(‘test.gradle’)
-
像Task配置方式一样,plugin也通过闭包的方式直接进行配置,可以修改其property,最终修改的配置会合并到同名的MyPlugin的extension上,所以value的值是’heheda’
(四)执行过程
运行./gradlew MyTask,结果如下:
project afterEvaluate
value is heheda
分析其执行过程可知,plugin的执行过程是这样的:
apply→new Plugin→plugin.apply→new Extension→new Task→extension.configure→afterEvaluate→task.doLast
四.Dependency
(一)基本使用
上面说过,每个项目都可以引入自己的第三方依赖库,我们最常使用的方式就是:
//构建时依赖库
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
//运行时依赖库
dependencies {
compile 'com.android.support:support-v4:25.3.1'
}
repositories {
mavenCentral()
}
-
构建时需要的依赖,一般声明在根项目的build.gradle的buildscript闭包中,该方法是project的一个方法,传入的闭包的代理对象会设置为一个ScriptHandler,其repositories方法设置了这些依赖的获取地址,gradle支持maven仓库、ivy仓库等,也可以自定义地址,比如本地地址等;其dependencies方法,就是加入一个依赖项,而依赖其实也分为不同的依赖组,比如我们常见的classpath就是指脚本构建时使用的依赖、compile为编译时的依赖、testCompile为测试时候的依赖等等,这些基本依赖组是gradle为我们定义好的。
-
运行时的依赖一般声明在每个project中的build.gradle中,其闭包的代理对象会设置为一个DependencyHandler,专门处理依赖项,其地址就是project的repositories里设置的(与ScriptHandler的不一样),添加的依赖项形式和构建时的一样,也是分为不同的依赖组
-
依赖项的命名方式就是group-module-version的结构,这就不用多说了
-
每个依赖项都会去repositories设置的地址去寻找group-module-version相同的依赖包进行下载解析
-
对于依赖项,gradle提供了几种模式进行依赖版本冲突的解决:
compile('com.android.support:appcompat-v7:25.3.1'){ exclude group: 'com.android.support', module: 'support-v4' } compile('com.android.support:appcompat-v7:25.3.1'){ force = true } compile('com.android.support:appcompat-v7:25.3.1'){ transitive = false }
-
exclude:不引入该依赖项里的制定依赖(group+module)
-
force=true:强制项目里的该依赖项的版本都为该声明的版本
-
transitive=false:依赖项取消递归引入,即该依赖所引入的其他依赖全部不引入
-
(二)自定义依赖
如果我们自己做了一个插件,想要上传为依赖怎么办呢?我们以上传到本地仓库为例来看一下
1.构建环境
创建一个项目,在其主项目的build.gradle:
//groovy的plugin用于构建groovy项目,默认sourceSets在src/main/groovy(resources)
apply plugin: 'groovy'
apply plugin: 'maven'
repositories {
mavenCentral()
}
dependencies {
compile gradleApi()
compile localGroovy()
}
group = 'com.cwj'
archivesBaseName = 'plugin'
version = '1.0'
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../../plugins'))
}
}
-
这里引入的plugin是groovy,是用来构建groovy项目的,而一般我们引用的都是com.android.application插件,用于构建android项目
-
声明group、archivesBaseName和version,对应于上述的group-name-version,用于声明自己依赖的路径
-
调用project的uploadArchives方法,传入闭包,设置上传仓库路径,这里上传到的是本地路径
2.编写源码
-
正如上面所说,构建的是groovy项目,其默认sourceSet是在src/main/groovy目录下,将我们的groovy文件写在这里即可
-
默认的资源文件目录是src/main/resources,我们需要在该目录下的META-INF/gradle-plugins目录下,创建一个dtplugin.properties文件:
implementation-class=com.cwj.plugin.DateAndTimePlugin
该文件名字即为外部引入本插件时的插件名字,而文件的内容,指向的就是实际插件的全限类名
3.上传
上传时,直接运行./gradlew uploadArchives命令,即可将plugin项目打包为依赖包(jar)并放到到指定本地路径下
4.应用
其他项目引入时,只需要指定仓库名和依赖项,即可拉取下来:
buildscript {
repositories {
maven {
//配置本地仓库
url uri('../plugins')
}
}
dependencies {
//引入本地plugin
classpath group: 'com.cwj', name: 'plugin', version: '1.0'
}
}
在使用时,名字用的就是上述的properties文件的名字
//应用
apply plugin: 'dtplugin'
//配置项
DateAndTime {
timeFormat = 'yyyy-MM-dd HH:mm:ss'
}
五.常用命令
如果表明具体projectName,则会执行其project的task,否则会执行所有项目的同名task
-
执行task,-q代表只输出error的log,其他log不输出
./gradlew -q {projectName:}taskName
-
执行task1,不执行task2
./gradlew task1 -x task2
-
添加运行时参数
./gradlew taskName -Ppro1=‘i am property1’
-
清除项目的build文件夹
./gradlew {projectName:}clean
-
执行构建任务
./gradlew assemble{Flavors}{BuildType}
-
查看项目的依赖树
./gradlew {projectName:}dependencies
-
查看项目脚本构建时的依赖树
./gradlew {projectName:}buildEnvironment
-
查看项目的所有task(–all包括自定义的)
./gradlew {projectName:}tasks {–all}
-
查看项目的项目列表
./gradlew {projectName:}projects
-
查看项目的所有属性
./gradlew {projectName:}properties
六.其他知识点
-
获取所有variant(debug/release/…)
project.extensions.getByType(AppExtension).applicationVariants
AppExtension是android{}块的extension,所以前提是应用了
apply plugin: 'com.android.application'
-
获取命令行所有任务名称(如./gradlew clean assembleDebug中的clean和assembleDebug)
project.gradle.startParameter.taskNames