「code小生」一个专注 Android 领域的技术平台
公众号回复 Android 加入我的安卓技术群
作者:孤独的根号十二
链接:https://www.jianshu.com/p/2bd920314012
声明:本文来自孤独的根号十二
投稿,转发等请联系原作者授权
Gradle介绍
Gradle是一个基于JVM的新一代构建工具,可以用于自动化自定义有序的步骤来完成代码的编译、测试和打包等工作,让重复的步骤变得简单,用于实现项目自动化,是一种可编程的工具,你可以用代码来控制构建流程最终生成可交付的软件。构建工具可以帮助你创建一个重复的、可靠的、无需手动介入的、不依赖于特定操作系统和IDE的构建
Gradle优势
1.Gradle结合Ant和Maven等构建工具的最佳特性。它有着约定优于配置的方法、强大的依赖管理,它的构建脚本使用Groovy或Kotlin 编写
2.Gradle 有非常良好的拓展性。如果你想要在多个构建或者项目中分享可重用代码,Gradle的插件会帮助你实现。将Gradle插件应用于你的项目中,它会在你的项目构建过程中提供很多帮助:为你的添加项目的依赖的第三方库、为你的项目添加有用的默认设置和约定(源代码位置、单元测试代码位置)。其中Android Gradle插件继承Java Gradle插件
3.Gradle可以使用Groovy来实现构建脚本,Groovy 是基于Jvm一种动态语言,它的语法和Java非常相似并兼容Java,因此你无需担心学习Groovy的成本。Groovy在Java的基础上增加了很多动态类型和灵活的特性,比起XML,Gradle更具有表达性和可读性。
4.Gradle提供了可配置的可靠的依赖管理方案。一旦依赖的库被下载并存储到本地缓存中,我们的项目就可以使用了。依赖管理很好的实现了在不同的平台和机器上产生相同的构建结果。
5.Gradle可以为构建你的项目提供引导和默认值,如果你使用这种约定,你的Gradle构建脚本不会有几行。
6.Gradle Wrapper是对Gradle 的包装,它的作用是简化Gradle本身的下载、安装和构建,比如它会在我们没有安装Gradle的情况下,去下载指定版本的Gradle并进行构建。Gradle的版本很多,所以有可能出现版本兼容的问题,这时就需要Gradle Wrapper去统一Gradle的版本,避免开发团队因为Gradle版本不一致而产生问题。
7.Gradle可以和Ant、Maven和Ivy进行集成,比如我们可以把Ant的构建脚本导入到Gradle的构建中
8.Gradle显然无法满足所有企业级构建的所有要求,但是可以通过Hook Gradle的生命周期,来监控和配置构建脚本。
9.社区的支持和推动
gradle 入门
gradle这个基于Groovy的DSL,DSL(Domain Specifc Language)意为领域特定语言,只用于某个特定的领域。我们只要按照Groovy的DSL语法来写,就可以轻松构建项目
task:
task(任务)和action(动作)是Gradle的重要元素。task代表一个独立的原子性操作,比如复制一个文件,编译一次Java代码,这里我们简单的定义一个名为hello的任务。doLast 代表task执行的最后一个action,通俗来讲就是task执行完毕后会回调doLast中的代码
task hello { doLast { println 'Hello world!' }}
println 'Hello world!'
}
}
也可以写成
task hello << { println 'Hello world!'}
}
操作符<< 是doLast方法的快捷版本
Gradle的任务
Gradle的任务,包括创建任务、任务依赖、 动态定义任务和任务的分组和描述
1 创建任务
1.1直接用任务名称创建。
def Task hello=task(hello)hello.doLast{ println "hello world"}
hello.doLast{
println "hello world"
}
1.2任务名称+任务配置创建
def Task hello=task(hello,group:BasePlugin.BUILD_GROUP)hello.doLast{ println "hello world"}
hello.doLast{
println "hello world"
}
1.3.TaskContainer的create方法创建。
tasks.create(name: 'hello') << { println "hello world"}'hello') << {
println "hello world"
}
1.4 通过上面dsl语法创建
任务依赖
任务依赖会决定任务运行的先后顺序,被依赖的任务会在定义依赖的任务之前执行。创建任务间的依赖关系如下所示。
task hello << { println 'Hello world!'}task go(dependsOn: hello) << { println "go for it"}
println 'Hello world!'
}
task go(dependsOn: hello) << {
println "go for it"
}
在hello任务的基础上增加了一个名为go的任务,通过dependsOn来指定依赖的任务为hello,因此go任务运行在hello之后。
3 .动态定义任务
动态定义任务指的是在运行时来定义任务的名称
3.times {number -> task "task$number" << { println "task $number" }}"task$number" << {
println "task $number"
}
}
times是Groovy在java.lang.Number中拓展的方法,是一个定时器。3.times中循环创建了三个新任务,隐式变量number的值为0,1,2,任务的名称由task加上number的值组成,达到了动态定义任务的目的。
运行gradle -q task0构建脚本
任务的分组和描述
Gradle有任务组的概念,可以为任务配置分组和描述,以便于更好的管理任务,拥有良好的可读性。
task hello { group = 'build' description = 'hello world' doLast { println "任务分组: ${group}" println "任务描述: ${description}" }}task go(dependsOn: hello) << { println "go for it"}
group = 'build'
description = 'hello world'
doLast {
println "任务分组: ${group}"
println "任务描述: ${description}"
}
}
task go(dependsOn: hello) << {
println "go for it"
}
Gradle日志级别
级别 用于
ERROR 错误消息
QUIET 重要的信息消息
WARNING 警告消息
LIFECYCLE 进度信息消息
INFO 信息性消息
DEBUG 调试消息
前面我们通过gradle -q +任务名称来运行一个指定的task,这个q是命令行开关选项,通过开关选项可以控制输出的日志级别。
开关选项 输出日志级别
无日志选项 LIFECYCLE及更高级别
-q或者 --quiet QUIET及更高级别
-i或者 --info INFO及更高级别
-d或者 --debug DEBUG及更高级别
Gradle 命令行
略
Gradle 的语法
1.声明变量
Groovy中用def关键字来定义变量,可以不指定变量的类型,默认访问修饰符是public。
def a = 1;def int b = 1;def c = "hello world";
def int b = 1;
def c = "hello world";
2.方法
方法使用返回类型或def关键字定义,方法可以接收任意数量的参数,这些参数可以不申明类型,如果不提供可见性修饰符,则该方法为public,如果指定了方法返回类型,可以不需要def关键字来定义方法,如果不使用return ,方法的返回值为最后一行代码的执行结果。
用def关键字定义方法。
task method <<{ add (1,2) minus 1,2 //1}def add(int a,int b) { println a+b //3} int minus(a,b) { return a-b }add (1,2)
minus 1,2 //1
}
def add(int a,int b) {
println a+b //3
}
int minus(a,b) {
return a-b
}
3.类
Groovy类非常类似于Java类。
task method <<{ def p = new Person() p.increaseAge 5 println p.age}class Person { String name Integer age =10 def increaseAge(Integer years) { this.age += years }}def p = new Person()
p.increaseAge 5
println p.age
}
class Person {
String name
Integer age =10
def increaseAge(Integer years) {
this.age += years
}
}
Groovy类与Java类有以下的区别:
默认类的修饰符为public。
没有可见性修饰符的字段会自动生成对应的setter和getter方法。
类不需要与它的源文件有相同的名称,但还是建议采用相同的名称。
4.语句
(1) 断言
Groovy断言和Java断言不同,它一直处于开启状态,是进行单元测试的首选方式。
task method <<{ assert 1+2 == 6}
assert 1+2 == 6
}
(2)for循环
Groovy支持Java的for(int i=0;i<N;i++)和for(int i :array)形式的循环语句,另外还支持for in loop形式,支持遍历范围、列表、Map、数组和字符串等多种类型
//遍历范围def x = 0for ( i in 0..3 ) { x += i}assert x == 6//遍历列表def x = 0for ( i in [0, 1, 2, 3] ) { x += i}assert x == 6//遍历Map中的值def map = ['a':1, 'b':2, 'c':3]x = 0for ( v in map.values() ) { x += v}assert x == 6
def x = 0
for ( i in 0..3 ) {
x += i
}
assert x == 6
//遍历列表
def x = 0
for ( i in [0, 1, 2, 3] ) {
x += i
}
assert x == 6
//遍历Map中的值
def map = ['a':1, 'b':2, 'c':3]
x = 0
for ( v in map.values() ) {
x += v
}
assert x == 6
(3)switch语句
task method <<{def x = 16def result = ""switch ( x ) { case "ok": result = "found ok" case [1, 2, 4, 'list']: result = "list" break case 10..19: result = "range" break case Integer: result = "integer" break default: result = "default"}assert result == "range"}16
def result = ""
switch ( x ) {
case "ok":
result = "found ok"
case [1, 2, 4, 'list']:
result = "list"
break
case 10..19:
result = "range"
break
case Integer:
result = "integer"
break
default:
result = "default"
}
assert result == "range"
}
5.数据类型
Groovy中的数据类型主要有以下几种:
Java中的基本数据类型
Groovy中的容器类
闭包
(1)字符串
在Groovy种有两种字符串类型,普通字符串String(java.lang.String)和插值字符串GString(groovy.lang.GString)
def name = 'Android进阶之光'println "hello ${name}"println "hello $name"task method <<{def name = '''Android进阶之光 Android进阶解密Android进阶?'''println name }'Android进阶之光'
println "hello ${name}"
println "hello $name"
task method <<{
def name = '''Android进阶之光
Android进阶解密
Android进阶?'''
println name
}
(2)List
Groovy没有定义自己的集合类,它在Java集合类的基础上进行了增强和简化。Groovy的List对应Java中的List接口,默认的实现类为Java中的ArrayList。
def number = [1, 2, 3] assert number instanceof List def linkedList = [1, 2, 3] as LinkedList assert linkedList instanceof java.util.LinkedListtask method <<{def number = [1, 2, 3, 4] assert number [1] == 2assert number [-1] == 4 //1 number << 5 //2 assert number [4] == 5assert number [-1] == 5}def name = [one: '魏无羡', two: '杨影枫', three: '张无忌'] assert name['one'] == '魏无羡' assert name.two == '杨影枫'2, 3]
assert number instanceof List
def linkedList = [1, 2, 3] as LinkedList
assert linkedList instanceof java.util.LinkedList
task method <<{
def number = [1, 2, 3, 4]
assert number [1] == 2
assert number [-1] == 4 //1
number << 5 //2
assert number [4] == 5
assert number [-1] == 5
}
def name = [one: '魏无羡', two: '杨影枫', three: '张无忌']
assert name['one'] == '魏无羡'
assert name.two == '杨影枫'
注释1处的索引-1是列表末尾的第一个元素。注释2处使用<<运算符在列表末尾追加一个元素
其他
String a = '23'int b = a as intdef c = a.asType(Integer)assert c instanceof java.lang.Integer'23'
int b = a as int
def c = a.asType(Integer)
assert c instanceof java.lang.Integer
Gradle Files
我们在AS中用到的Gradle其实应该被叫做 Android Gradle Plugin,也就是安卓项目上的gradle插件;
Gradle插件会有版本号,每个版本号又对应有一个或一些 Gradle发行版本(一般是限定一个最低版本),也就是我们常见的类似gradle-3.1-all.zip这种东西;
如果这两个版本对应不上了,那你的工程构建的时候就会报错。
![640?wx_fmt=other](https://i-blog.csdnimg.cn/blog_migrate/403b6aa3babd5666b4ee9f08a9633670.jpeg)
Android Studio 3.0 之后自动将插件版本升级到3.0.0,所以我们也需要对应地把Gradle升级到4.1才行
另外, Android Gradle Plugin又会跟 Android SDK BuildTool有关联,因为它还承接着AndroidStudio里的编译相关的功能,这也是我们要在项目的 local.properties 文件里写明Android SDK路径、在build.gradle 里注明 buildToolsVersion 的原因。
所以 Android Gradle Plugin 本质上就是 一个AS的插件,它一边调用 Gradle本身的代码和批处理工具来构建项目,一边调用Android SDK的编译、打包功能,从而让我们能够顺畅地在AS上进行开发。
![最基础的文件配置 640?wx_fmt=other](https://i-blog.csdnimg.cn/blog_migrate/8c3e7ac86a2c8acf8050007e92bf48bb.jpeg)
在android studio中,没有类似于 Eclipse 工作空间(Workspace)的概念,而是提出了Project和Module这两个概念,Project是最顶级的结构单元,然后就是Module,一个Project可以有多个Module。目前,主流的大型项目结构基本都是多Module的结构,这类项目一般是按功能划分的。一个Project是由一个或多个Module组成,尽量让各模块处于同一项目之中,此时彼此之间具有互相依赖的关联关系,在一般情况下,Android是默认单Project单Module的,这时Project和Module合二为一,在没有修改存储路径的时候,显然Project对Module具有强约束作用。
我们可以简单的理解为:一个Project代表一个完整的APP,Module表示APP中的一些依赖库或独立开发的模块。比如可以新建一个library做为module,然后在主APP上点右键 open module setting的Dependencies中添加一个模块依赖。然后主APP中就可以使用module中的类了
一个项目有一个setting.gradle、包括一个顶层的Project的 build.gradle文件、每个Module 都有自己的一个build.gradle文件,android studio默认创建一个app的Module。
setting.gradle:这个 setting 文件定义了哪些module 应该被加入到编译过程,对于单个module 的项目可以不用需要这个文件,但是对于 multimodule 的项目我们就需要这个文件,否则gradle 不知道要加载哪些项目。这个文件的代码在初始化阶段就会被执行。
顶层的project的build.gradle文件的配置最终会被应用到所有项目中。
buildscript:定义了 Android 编译工具的类路径。repositories中,jCenter是一个著名的 Maven 仓库。
dependencies { classpath 'com.android.tools.build:gradle:3.1.0' 这里配置gradle的插件来编译gradle文件,同时也可以配置其他插件,gradle插件的版本需要与gradle-wrapper.properties中gradle版本对应}
classpath 'com.android.tools.build:gradle:3.1.0' 这里配置gradle的插件来编译gradle文件,同时也可以配置其他插件,gradle插件的版本需要与gradle-wrapper.properties中gradle版本对应
}
allprojects
:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。apply plugin
:第一行代码应用了Android 程序的gradle插件,作为Android 的应用程序,这一步是必须的,因为plugin中提供了Android 编译、测试、打包等等的所有task。每个项目单独的 build.gradle
:针对每个moudle 的配置,如果这里的定义的选项和顶层build.gradle定义的相同,后者会被覆盖。android
:这是编译文件中最大的代码块,关于android 的所有特殊配置都在这里,这就是又我们前面的声明的 plugin 提供的。defaultConfig
就是程序的默认配置,注意,如果在AndroidMainfest.xml里面定义了与这里相同的属性,会以这里的为主。
这里最有必要要说明的是applicationId的选项:在我们曾经定义的AndroidManifest.xml中,那里定义的包名有两个用途:一个是作为程序的唯一识别ID,防止在同一手机装两个一样的程序;另一个就是作为我们R资源类的包名。在以前我们修改这个ID会导致所有用引用R资源类的地方都要修改。但是现在我们如果修改applicationId只会修改当前程序的ID,而不会去修改源码中资源文件的引用。buildTypes
:定义了编译类型,针对每个类型我们可以有不同的编译配置,不同的编译配置对应的有不同的编译命令。默认的有debug、release 的类型。
可以通过配置buildConfigField设置一些key-value对,这些key-value 对在不同编译类型的 apk 下的值不同,比如我们可以为debug 和release 两种环境定义不同的服务器,还可以通过manifestPlaceholders 修改manifest里面meta-data的值
buildTypes { debug { minifyEnabled false shrinkResources false manifestPlaceholders = [aaaa: "122222423524123139666"] proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField("String","URL","http://www.baidu.com") } release { minifyEnabled true shrinkResources true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' manifestPlaceholders = [aaaa: "12222133655466889666"] buildConfigField("String","URL","http://www.google.com") } }String host = BuildConfig.url;//这里取buildConfigField的值//manifest里面配置 <meta-data android:name="JPUSH_APPKEY" android:value="${aaaa}" />
minifyEnabled false
shrinkResources false
manifestPlaceholders = [aaaa: "122222423524123139666"]
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String","URL","http://www.baidu.com")
}
release {
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
manifestPlaceholders = [aaaa: "12222133655466889666"]
buildConfigField("String","URL","http://www.google.com")
}
}
String host = BuildConfig.url;//这里取buildConfigField的值
//manifest里面配置
<meta-data
android:name="JPUSH_APPKEY"
android:value="${aaaa}" />
dependencies
:是属于gradle 的依赖配置。它定义了当前项目需要依赖的其他库。
Gradle Wrapper
radle 不断的在发展,新的版本难免会对以往的项目有一些向后兼容性的问题,这个时候,gradle wrapper就应运而生了。
gradlw wrapper 包含一些脚本文件和针对不同系统下面的运行文件。wrapper 有版本区分,但是并不需要你手动去下载,当你运行脚本的时候,如果本地没有会自动下载对应版本文件。
在不同操作系统下面执行的脚本不同,在 Mac 系统下执行./gradlew …,在windows 下执行gradle.bat进行编译。
如果你是直接从eclipse 中的项目转换过来的,程序并不会自动创建wrapper脚本,我们需要手动创建。在命令行输入以下命令即可
gradle wrapper --gradle-version 2.4wrapper --gradle-version 2.4
它会创建如下目录结构:
![640?wx_fmt=other](https://i-blog.csdnimg.cn/blog_migrate/6bccb3666289a4d821031b27a43e952c.jpeg)
推荐阅读