Android Gradle基础

160 阅读7分钟

一、概述

Gradle是一种项目自动化构建工具,而Gradle文件是用Groovy语言编写的xml文件形式的脚本。

Groovy是用于Java虚拟机(JVM)的一种敏捷的动态语言(DSL),面向对象的一种编程语言,同时又是一种脚本语言,代码书写规则简单,包含闭包和一般动态语言的其他特性。

二、Groovy语言

从w3cschool的中文网站可以找到Groovy的教程,其中Groovy的特点有如下描述:

Groovy中有以下特点:

  • 同时支持静态和动态类型。
  • 支持运算符重载。
  • 本地语法列表和关联数组。
  • 对正则表达式的本地支持。
  • 各种标记语言,如XML和HTML原生支持。
  • Groovy对于Java开发人员来说很简单,因为Java和Groovy的语法非常相似。
  • 您可以使用现有的Java库。
  • Groovy扩展了java.lang.Object。

Groovy与java

  • 完全兼容Java的语法;
  • 方法和类都是默认public的;
  • 编译器会自动给属性添加get/set方法;
  • 属性可以通过直接用点号获取,类似于java的静态变量;
  • 最后一个表达式的值是返回值;
  • 不会有空指针错误;
  • 分号并不是必须的;

特性

  • 任何地方可以执行assert断言语句;
  • 类型不必明确定义,根据赋值类型定义属性类型;
  • 调用方法时可以不写括号;

1、字符串

字符串的三种表达式:”string” ‘string’ ```string”’,三种方式的区别:

'':单纯的字符串;
"":可以插入变量,比如: def a = world    打印时插入变量a:"hello ${a} !!!" 
'''''':可以保留回车的换行,比如:
            '''hello 
            world !!!'''
        打印结果也是这样

测试的代码

println('Hello world')
def str = "world"
println("Hello ${str}")
println('''Hello
world
''')

打印结果

> Configure project :
Hello world
Hello world
Hello
world

延伸,Groovy原生Api可以去掉小括号,非原生Api支持不是很友好,保险起见最好保留小括号

println("AAAA")
//等价于
println "AAAA"   //这种写法在gradle文件很常见

2、集合

list 相当于java中的 Arraylist

def list = ['java','gradle','groovy'] 
list << 'c++' //追加到list中
调用方法:list[0]

map 相当于java的 LinkHashMap

def map = ['javaver':1.8,'gradlever':4.1] 
map.groovy=2.4.15 // 追加元素 
调用方法:map.javaver

3、闭包

闭包(Closure)简单的理解就是 方法内部定义内部方法,而这个内部方法提高给外部使用

1)直接调用闭包

//打印方法,def为未定义的返回值,这里写确定的返回值void也可以
def methodPrint(String str) {
    println(str)
}

//这个闭包不需要传递参数
def methodA = {
    methodPrint("methodA print")
}

//这个闭包需要传入一个参数t,不传会报错,->写法与java的lambda相似
def methodB = {
    t -> methodPrint(t)
}

//调用
//虽然不需要传参,但是小括号不能省略
methodA()
//需要传参,小括号可以省略
methodB "methodB print"

2)闭包作为参数传递(闭包内不需要传入参数)

//打印方法,def为未定义的返回值,这里写确定的返回值void也可以
def methodPrint(String str){
    println(str)
}

//定义的闭包
//闭包对象methodA可以当作参数使用,花括号内是可以执行的逻辑代码
def methodA  = {
    methodPrint("methodA print")
}

//这个方法需要传入一个闭包做参数
def methodC(Closure closure){
    //没有给闭包传递参数
    closure()
}

//调用
methodC(methodA)
//或去掉小括号调用,二种写法结果一样,但有说法Groovy对非原生Api去掉小括号支持不是很友好,建议保留
methodC methodA

3)闭包作为参数传递(闭包内需要传入参数)

//打印方法,def为未定义的返回值,这里写确定的返回值void也可以
def methodPrint(String str){
    println(str)
}

//这个闭包需要传入一个参数t,不传会报错,->写法与java的lambda相似
def methodB = {
    t -> methodPrint(t)
}

//这个方法需要传入一个闭包做参数
def methodD(Closure closure){
    //闭包内传递了一个参数
    closure("Hello world")
}

//调用
methodD methodB

4)其他各种写法

//打印方法,def为未定义的返回值,这里写确定的返回值void也可以
def methodPrint(String str) {
    println(str)
}

def methodA(Closure closure) {
    closure()
}

def methodB(String content, Closure closure) {
    closure(content)
}

//调用methodA
methodA {
    methodPrint("methodA type-->0")
}
methodA({ methodPrint("methodA type-->1") })

//调用methodB
methodB("methodB type-->0", { t -> methodPrint t })
methodB "methodB type-->1", { t -> methodPrint t }
methodB("methodB type-->2") { t -> methodPrint t }

4、methodMissing

methodMissing的存在扩展了方法的定义,Android中最典型的用处就是多渠道包的扩展,比如除了debug,release,还可以扩展xiaomi,huawei等gradle的Android插件不存在的方法。

methodMissing简单示例:

//调用:CustomMethod类中并没有定义methodA这个方法
new CustomMethod().methodA()

class CustomMethod implements GroovyInterceptable {
    //定义一个打印的方法
    def methodPrint(String str) {
        println(str)
    }
    
    //定义methodMissing的兼容
    def methodMissing(String name, def args) {
        if (name == "methodA") {  //扩展
            methodPrint "methodA run from methodMissing"
        }
    }
}

打印结果

> Configure project :
methodA run from methodMissing

上述示例中,CustomMethod类当然不知道我要定义methodA方法,所以这个只是一个简单的示例,说明一种方法扩展形式,在实际开发中一般是检测到不存在的方法时会先doAdd,然后再create这个方法,具体可以阅读源码,因为时间有限这里不展开。

methodMissing部分源码 image.png

doAdd部分源码
image.png

三、Gradle构建脚本

1、构建块

gradle构建中的有两个基本的概念:项目(project)、任务(task),每个构建块至少包含一个project、project中包含一个或多个task。在多项目构建中,一个项目可以依赖于其他项目,任务也可以形成一个依赖关系图来确保他们的执行顺序。

image.png 解读:Project1中TaskA依赖于TaskB,TaskB依赖于TaskC,Project2中TaskD依赖于TaskE,TaskE依赖于TaskF,而Project1依赖于Project2,所以构建顺序应该是TaskF-E-D-C-B-A;

2、项目(project)

概念:一个项目代表一个正在构建的组件(如:jar、aar等),构建启动后,gradle会基于build.gradle实例化一个org.gradle.api.Project对象,并且能够通过project变量使其隐式可用,build.gradle中编写的变量、方法等脚本都会在这个project对象中运行; 什么是隐式调用?举个栗子:

在build.gradle中
group 'com.ailian.test' 这个就是隐式调用 
原版应该是: project.group = 'com.ailian.test'

属性:
group 组
name 名称,一个组不能重名
version 版本号,构建出project的版本号
通过以上三个属性可以唯一确定一个对象;

  //比如:gson的依赖就是这样被唯一确定的
  compile 'com.google.code.gson:gson:2.8.4'
  //完整写法
  compile group:'com.google.code.gson',name:'gson',version:'2.8.4'
  //拆开
  group : com.google.code.gson
  name : gson
  verson : 2.8.4

方法:
apply 运用插件
dependencies 依赖
reprositories 仓库
task 任务
属性的其他配置方式:ext(extra property额外属性)、gradle.properties(键值对方式定义属性)

根build.gradle有一个根project(rootProject)

3、任务

概念:任务对应org.gradle.api.Task。主要包括任务动作和任务依赖。任务动作定义了一个最小的工作单元,可以定义依赖于其他任务、动作执行顺序和执行条件。

方法:
dependsOn 声明任务依赖
doFirst 任务列表最前面执行的任务
doLast或<< 任务列表最后面执行的任务
一个任务列表可以执行多次doFirst和doLast方法\

举例,定义task a,task b:

task ta() {   //task a
    println("ta")
}
 
task tb(dependsOn: ta) {   //task b 依赖 task a
    doFirst { println("doFirst") }
    println("tb")
    doLast { println("doLast") }
}

运行task b会得到如下结果:

> Configure project :   //这里是Configure project阶段
ta
tb

> Task :ta UP-TO-DATE   //任务执行阶段

> Task :tb    //任务执行阶段
doFirst
doLast

BUILD SUCCESSFUL in 209ms

可以看到 ta,tb在前面就打印了,而不是tb夹在doFirst和doLast中间打印,这是因为任务执行前需要初始化来定义任务的执行顺序,形成任务线,初始化阶段ta,tb已经被打印出来了,doFirst和doLast在任务真正的执行阶段才会被打印。

四、Gradle构建生命周期

  • 初始化
    初始化需要参与到构建中的项目
  • 配置
    生成task的依赖顺序和执行顺序
  • 执行
    执行动作代码

image.png

五、Gradle构建的依赖

几乎所有基于JVM的软件项目都需要依赖外部类库来重用现有的功能,自动化的依赖管理可以明确依赖的版本,可以解决因传递性依赖带来的版本冲突。

1、常用仓库

公共仓库:可以上传依赖包和下载依赖包
mavenCentral :search.maven.org
jcenter :jcenter.bintray.com/
google
私有仓库:mavenLocal 本地已有的依赖包
自定义仓库:公司内部的仓库

如:build.gradle中的配置
repositories{
 maven{
      url "https://oss.sonatype.org/content/repositories/snapshots"//自定义仓库地址
  }
  mavenLocal()
  mavenCentral()
  jcenter()

}
根据上面的配置顺序会依次从上面的仓库查找依赖包,找到为止,找不到则报错

2、依赖的传递性

B依赖A,C依赖B,则C依赖A

正因为有版本的传递性,才会有依赖冲突的问题

比如: 
B依赖A:1.2版本
C依赖A:1.3版本
D依赖B和C 
根据依赖的传递性,D依赖A,这时A有两个版本1.21.3,这时候就出现了冲突

比如依赖Gson库 image.png

依赖传递性从另一个角度看可以尽量减少已有的依赖避免依赖版本冲突的问题;
比如:A模块依赖了Gson包(api形式),主工程B依赖A模块,主工程B就不需要单独依赖Gson包了。

解决版本冲突的方法:

1、排除传递性依赖

dependencies{
     compile('com.jakewharton.rxbinding2:rxbinding-design:2.0.0'){
     exclude group: 'com.android.support'
    } 
 }
 
 编译rxbinding-design时排除依赖包“com.android.support

2、强制指定一个版本

configurations.all{
   resolutionStrategy{
       force 'com.android.support:appcompat-v7:26.0.0'
   }
}

项目中所有使用com.android.support:appcompat-v7的包都强制使用26.0.0的版本

参考博客

内容主要来自于这篇博客:
Gradle学习之基础篇 作者:小火你好
其他参考博客:
Gradle学习系列 作者:renxhui

--个人学习笔记