这段时间来学习了gradle,也体会到了gradle从初步理解到基本熟悉,再到深刻源码这样一个过程当中的一些曲折。因而就萌发了写这样一篇逐步深刻原理的文章的想法。html
这篇文章主要是gradle的基础知识篇。看完这篇文章,你能够:java
若是你想关注gradle更深刻的一些知识,请继续关注后续gradle文章。python
先来看一段维基百科上对于gradle的解释。android
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划将来将支持更多的语言。shell
可能刚接触gradle的同窗都不是很了解gradle的这个定义。可能就只会跟着网上的教程copy一点配置,可是不理解这些配置背后的原理。那么怎么来理解这句话呢,咱们能够把握到三个要点:首先,它是一种构建工具
,其次,gradle是基于maven概念
的,最后,使用groovy
这种语言来声明。要理解这几句话,咱们先考虑几个场景。缓存
1.渠道管理
:国内手机市场有大大小小数十个,大的手机厂商也有五六个,每一个厂商可能又有不一样的定制rom。若是咱们要为不一样市场和厂商进行适配,那就须要写这样的代码安全
if(isHuawei) {
// dosomething
} else if(isOppo) {
// dosomething
}
复制代码
这样的话,繁琐不说,对单个手机而言大量的无用代码被编译进apk中,包体积和运行速度都会受影响。为了解决这个问题,gradle引进了productFlavor和buildType的能力,能根据状况来进行打包。因此说他是一个自动化构建工具
。能够看官方文档bash
2.依赖管理
:咱们一般会在项目中引入各类三方库进行代码复用。好比,直接手动把jar或者aar copy到项目中,而后添加依赖。这种方法缺陷很明显,首先配置和删除流程很繁琐,其次,同一个jar可能会被多个项目所引用,致使不知不觉就copy了多个jar。最后,版本管理艰难。为了解决这个问题,gradle是基于maven仓库,配置和删除的时候仅须要对仓库的坐标进行操做,全部的库都会被gradle统一管理,大多数状况下每一个库只会有一个版本存在于项目中,而且每一个库只会有一个副本存在于项目中。闭包
因此gradle其实不是什么神秘的东西,只是基于某种语言(groovy, java, kotlin)的一种构建工具而已。只要咱们大概掌握了基本的用法和他的内部原理,平常工做中就会知道本身网上搜到的命令是什么意思啦。skr~app
我们先看看平常工做中常常用到的几个gradle文件。能够看到主要有有三个文件: 1.build.gradle 根文件下放的一般放的是针对整个工程的通用配置,每一个module下面的build.gradle文件是针对每一个module自身的配置。
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
复制代码
这是一个默认的配置,咱们能够看到有buildscript,allprojects,repositories,dependencies几个配置项,这些配置项是干吗的呢,不少的同窗在刚学gradle的时候都是一脸懵逼的。这些实际上是gradle的一种特定的语法,咱们称之为DSL(domain-specific language)。能够参考官网。这里能够看到allprojects代理的是每一个project,能够理解成咱们的每一个module,也就是对咱们所写的每一个module的配置。buildscript主要配置的是打包相关的东西,好比gradle版本,gradle插件版本等,这些都是针对构建工具本身的配置。repositories,dependencies是三方库的仓库和坐标。因此根目录的build.gradle至关因而总体的配置。
而module下的build.gradle主要是android,dependencies等配置项。
apply plugin: 'com.android.application'
android{
...
}
dependencies{
...
}
复制代码
可能有些同窗会感到奇怪,为啥咱们在官网没有看到android这个配置项呢?这个主要是由于它并非gradle的DSL,某种意义上说应该算是android特有的,是经过Android的插件'com.android.application'带进来的配置项。咱们若是把第一行删掉,就会发现android{}这个配置项找不到了。
因此,咱们能够发现,build.gradle里面的配置项,要么是gradle自带的,要么是各类插件定义的。有不认识的配置项,就去官网查询一下就行了,授人以鱼不如授人以渔嘛。咱们后面也会讲解到引进插件的方式和怎么定义插件和配置项。
2.settings.gradle 这个文件主要是决定每一个module是否参与构建。咱们能够这样去理解,settings.gradle至关因而每一个module的开关,关上了这个module就不能使用了,别的依赖到它的module也都会出问题。
3.gradle.properties 这里主要是增长和修改一些能够在构建过程当中直接使用的参数。不仅是能够添加自定义参数,还能够修改系统的参数哦~
总结一下,就是说根目录下有一个build.gradle,处理整个工程的配置项,根目录下的settings.gradle配置整个工程中参与构建的module,每一个module本身有一个build.gradle,处理本身模块的配置。这就是android构建的一个大概状况。固然,看了这一部分确定仍是不懂怎么去写的,接下来咱们走进代码层面。
gradle可使用groovy,kotlin,java等语言进行书写,可是groovy相对来讲是目前比较流行的gradle配置方式,下面咱们讲解一点groovy基础。不讲太多,够用就行。
1.字符串
groovy的字符串分为两种java.lang.String和groovy.lang.GString。其中单引号和三引号是String类型的。双引号是GString类型的。支持占位插值操做。和kotlin同样,groovy的插值操做也是用${}
或者$
来标示,${}
用于通常替代字串或者表达式,$
主要用于A.B的形式中。
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
println eagerGString
println lazyGString
number = 2
println eagerGString
println lazyGString
复制代码
2.字符Character
Groovy没有明确的Character。可是能够强行声明。
char c1 = 'A'
assert c1 instanceof Character
def c2 = 'B' as char
assert c2 instanceof Character
def c3 = (char)'C'
assert c3 instanceof Character
复制代码
4.List
Groovy的列表和python的很像。支持动态扩展,支持放置多种数据。使用方法支持def和直接定义。还能够像python那样索引
//List中存储任意类型
def heterogeneous = [1, "a", true]
//判断List默认类型
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
//使用as强转类型
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
//定义指定类型List
LinkedList otherLinked = [3, 4, 5]
assert otherLinked instanceof java.util.LinkedList
// 像python同样索引
assert letters[1] == 'b'
//负数下标则从右向左index
assert letters[-1] == 'd'
assert letters[-2] == 'c'
//指定item赋值判断
letters[2] = 'C'
assert letters[2] == 'C'
//给List追加item
letters << 'e'
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//获取一段List子集
assert letters[1, 3] == ['b', 'd']
assert letters[2..4] == ['C', 'd', 'e']
复制代码
5.Map
//定义一个Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
//获取一些指定key的value进行判断操做
assert colors['red'] == '#FF0000'
assert colors.green == '#00FF00'
复制代码
6.运算符
*.
展开运算符,一个集合使用展开运算符能够获得一个元素为原集合各个元素执行后面指定方法所得值的集合。cars = [
new Car(make: 'Peugeot', model: '508'),
null,
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']
assert null*.make == null
复制代码
7.闭包 groovy里比较重要的是闭包的概念。官方定义是“Groovy中的闭包是一个开放,匿名的代码块,能够接受参数,返回值并分配给变量”。 其实闭包跟kotlin的lambda函数很像,都是先定义后执行。可是又有一些细微的区别。接下来咱们细讲讲gradle的闭包。
闭包是能够用做方法参数的代码块,Groovy的闭包更象是一个代码块或者方法指针,代码在某处被定义而后在其后的调用处执行。一个闭包实际上就是一个Closure类型的实例。写法和kotlin的lambda函数很像。
咱们常见的闭包是这样的
//最基本的闭包
{ item++ }
//使用->将参数与代码分离
{item -> item++ }
//使用隐含参数it
{ println it }
//使用显示的名为参数
{ name -> println name }
// 调用方法
a.call()
a()
// Groovy的闭包支持最后一个参数为不定长可变长度的参数。
def multiConcat = { int n, String... args ->
args.join('')*n
}
复制代码
你们要注意,若是咱们单纯的只是写成 a = { item++ }, 这只是定义了一个闭包,是不能运行的。必须调用a.call()才能运行出来。因此你们能够理解了,闭包就是一段代码块而已。当咱们有须要的时候,能够去运行它,这么一想是否是和lambda函数很像?
若是你看了官网,你会发现有一些这样的说法,
什么叫作delegate?这里涉及到闭包内部的三种对象。
this和owner都比较好理解。咱们能够用闭包的getxxx方法获取
def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
复制代码
重头戏仍是delegate这个对象。闭包能够设置delegate对象,设置delegate的意义就是将闭包和一个具体的对象关联起来。 咱们先来看个例子,这里以自定义android闭包为例。
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
复制代码
这个闭包对应的实体类是两个。
# Android.groovy
class Android {
private int mCompileSdkVersion
private String mBuildToolsVersion
private ProductFlavor mProductFlavor
Android() {
this.mProductFlavor = new ProductFlavor()
}
void compileSdkVersion(int compileSdkVersion) {
this.mCompileSdkVersion = compileSdkVersion
}
void buildToolsVersion(String buildToolsVersion) {
this.mBuildToolsVersion = buildToolsVersion
}
void defaultConfig(Closure closure) {
closure.setDelegate(mProductFlavor)
closure.setResolveStrategy(Closure.DELEGATE_FIRST)
closure.call()
}
@Override
String toString() {
return "Android{" +
"mCompileSdkVersion=" + mCompileSdkVersion +
", mBuildToolsVersion='" + mBuildToolsVersion + '\'' + ", mProductFlavor=" + mProductFlavor + '}' } } # ProductFlavor.groovy class ProductFlavor { private int mVersionCode private String mVersionName private int mMinSdkVersion private int mTargetSdkVersion def versionCode(int versionCode) { mVersionCode = versionCode } def versionName(String versionName) { mVersionName = versionName } def minSdkVersion(int minSdkVersion) { mMinSdkVersion = minSdkVersion } def targetSdkVersion(int targetSdkVersion) { mTargetSdkVersion = targetSdkVersion } @Override String toString() { return "ProductFlavor{" + "mVersionCode=" + mVersionCode + ", mVersionName='" + mVersionName + '\'' + ", mMinSdkVersion=" + mMinSdkVersion + ", mTargetSdkVersion=" + mTargetSdkVersion + '}' } } 复制代码
而后定义的时候就写成
//闭包定义
def android = {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
//调用
Android bean = new Android()
android.delegate = bean
android.call()
println bean.toString()
//打印结果
Android{mCompileSdkVersion=25, mBuildToolsVersion='25.0.2', mProductFlavor=ProductFlavor{mVersionCode=1, mVersionName='1.0', mMinSdkVersion=15, mTargetSdkVersion=25}}
复制代码
这样就能将闭包中声明的值,赋给两个对象Android和ProductFlavor来处理了。
上面官网的图里,说ScriptHandler被设置成buildscript的delegate。意思就是buildscript定义的参数被ScriptHandler拿来使用了。你们有兴趣的能够去看看ScriptHandler的源码~
上面咱们讲完了基本的用法,你们可能懂gradle的配置和写法了。可是可能仍是不懂gradle的构建体系究竟是怎么样的。这里咱们就要深刻进gradle的构建体系Project和Task了。下面的东西看着就要动动脑筋了。
1.Task Task是gradle脚本中的最小可执行单元。类图以下:
值得注意的是由于Gradle构建脚本默认的名字是build.gradle,当在shell中执行gradle命令时,Gradle会去当前目录下寻找名字是build.gradle的文件。因此只有定义在build.gradle中的Task才是有效的。
能够经过三种方式来声明task。咱们能够根据本身的项目须要去定义Task。好比自定义task接管gradle的编译过程
task myTask2 << {
println "doLast in task2"
}
//采用 Project.task(String name) 方法来建立
project.task("myTask3").doLast {
println "doLast in task3"
}
//采用 TaskContainer.create(String name) 方法来建立
project.tasks.create("myTask4").doLast {
println "doLast in task4"
}
复制代码
TaskContianer 是用来管理全部的 Task 实例集合的,能够经过 Project.getTasks() 来获取 TaskContainer 实例。 常见接口:
findByPath(path: String): Task getByPath(path: String): Task getByName(name: String): Task withType(type: Class): TaskCollection matching(condition: Closure): TaskCollection //建立task create(name: String): Task create(name: String, configure: Closure): Task create(name: String, type: Class): Task create(options: Map<String, ?>): Task create(options: Map<String, ?>, configure: Closure): Task //当task被加入到TaskContainer时的监听 whenTaskAdded(action: Closure) 复制代码
Gradle支持增量编译。了解过编译profile文件的朋友都知道,里面有大量的task都是up-to-date
。那么这种up-to-date是什么意思呢。Gradle的Task会把每次运行的结果缓存下来,当下次运行时,会检查一个task的输入输出有没有变动。若是没有变动就是up-to-date,跳过编译。
2.Project 先从Project对象讲起,Project是与Gradle交互的主接口。android开发中最为咱们所熟悉的就是build.gradle文件,这个文件与Project是一对一的关系,build.gradle文件是project对象的委托,脚本中的配置都是对应着Project的Api。Gradle构建进程启动的时候会根据build.gradle去实例化Project类。也就是说,构建的时候,每一个build.gradle文件会生成一个Project对象,这个对象负责当前module的构建。
Project本质上是包含多个Task的容器,全部的Task存在TaskContainer中。咱们从名字能够看出
能够看到dependencies, configuration, allprojects, subprojects, beforeEvaluate, afterEvaluate这些都是咱们常见的配置项,在build.gradle文件中接收一个闭包Closure。
好了,如今咱们已经聊了build.gradle了,可是你们都知道,咱们项目中还有一个settings.gradle呢,这个是拿来干吗的呢?这就要说到Project的Lifecycle
了,也就是Gradle构建Project的步骤,看官网原文:
Project.evaluationDependsOnChildren()
or by adding an explicit evaluation dependency using Project.evaluationDependsOn(java.lang.String)
.也就是说,Project对象依赖Settings对象的构建。咱们常在settings.gradle文件中配置须要引入的module,就是这个缘由。
3.Property 看完了build.gradle和settings.gradle,接下来咱们讲讲gradle.properties。这个文件存放的键值对形式的属性,这些属性能被项目中的gradle脚本使用ext.xxx所访问。
咱们也可使用Properties类来动态建立属性文件。如:
def defaultProps = new Properties()
defaultProps.setProperty("debuggable", 'true')
defaultProps.setProperty("groupId", GROUP)
复制代码
而且属性能够继承,在一个项目中定义的属性能够自动被子项目继承。因此在哪一个子项目均可以使用project.ext.xxx访问。不一样子项目间采用通用的配置插件来配置
apply from: rootProject.file('library.gradle')
复制代码
经过上面的学习,你们应该已经了解了gradle的基本配置,写法和比较浅显的内部原理了。由于篇幅缘由,深刻的内容咱们放在下一篇。敬请期待《一篇文章深刻gradle》
我是Android笨鸟之旅,一个陪着你慢慢变强的公众号。