一、概念
APK的构建过程
APK的构建过程比较繁琐,主要分为以下几步:
1.通过AAPT(Android Asset Packaging Tool)打包res资源文件,比如AndroidManifest.xml、xml布局文件等,并将这些xml文件编译为二进制,其中assets和raw文件夹的文件不会被编译为二进制,最终会生成R.java和resources.arsc文件。
2.AIDL工具会将所有的aidl接口转化为对应的Java接口。
3.所有的Java代码,包括R.java和Java接口都会被Java编译器编译成.class文件。
4.Dex工具会将上一步生成的.class文件、第三方库和其他.class文件编译成.dex文件。
5.上一步编译生成的.dex文件、编译过的资源、无需编译的资源(如图片等)会被ApkBuilder工具打包成APK文件。
6.使用Debug Keystore或者Release Keystore对上一步生成的APK文件进行签名。
7.如果是对APK正式签名,还需要使用zipalign工具对APK进行对齐操作,这样应用运行时会减少内存的开销。
Gradle
Gradle是一个自动化构建工具,通过组织一系列Task来最终完成自动化构建,所以Task是Gradle里最重要的概念。我们以生成一个可用的apk为例,整个过程要经过资源的处理、javac编译、dex打包、apk打包、签名等步骤,每个步骤就对应到Gradle里的一个Task。Gradle可以类比作一条流水线,Task可以比作流水线上的机器人,每个机器人负责不同的事情,最终生成完整的构建产物。
Gradle脚本使用了Groovy或者Kotlin DSL(Domain Specific Language),不过目前还是Groovy居多。对Gradle脚本来说,DSL实现了简洁的定义,又有充分的语言逻辑,以android {}为例,这本身是一个函数调用,参数是一个闭包,但是这种定义方式明显要简洁很多。
Gradle基于Groovy编写,而Groovy是基于JVM的语言,本质上是面向对象的语言。面向对象语言的特点就是一切皆对象,所以在Gradle里,.gradle脚本的本质就是类的定义,一些配置项的本质都是方法调用,参数是后面的{}闭包。比如build.gradle对应Project类,buildScript对应Project.buildScript方法。
Android Gradle插件
Gradle跟Android Studio其实没有关系,Google在推出Android Studio的时候选中了Gradle作为构建工具,为了支持Gradle能在Android Studio上使用,Google做了个Android Studio的插件,叫做Android Gradle插件,所以我们能在Android Studio上使用Gradle完全是因为这个插件的原因。
在项目根目录有个build.gradle文件,里面有这么一句代码:
classpath 'com.android.tools.build:gradle:3.2.1'
这个就是依赖Android Gradle插件的代码,后面的版本号代表的是Android Gradle插件的版本,而不是Gradle的版本,这个是Google定的,跟Gradle官方没关系。
Gradle Wrapper
Gradle Wrapper意为Gradle的包装,假设我们本地有多个项目,一个是比较老的项目用着Gradle 1.0的版本,一个是比较新的项目用了Gradle 2.0的版本,但是两个项目肯定都想要同时运行的,如果只装了Gradle 1.0的话那肯定不行,所以为了解决这个问题,Google推出了Gradle Wrapper的概念,就是在每个项目都配置了一个指定版本的Gradle,这样就可以支持用不同的Gradle版本来构建项目。
Wrapper在Window下是一个批处理脚本,在Linux下是一个shell脚本,当使用wrapper启动Gradle的时候,wrapper会检查Gradle有没有被下载关联,如果没有将会从配置的地址进行下载并运行构建。
查看当前项目所用Gradle版本的方法:使用终端切换到项目根目录,输入:gradlew -v。
Project Task Action
每次构建(build)至少由一个Project构成,一个Project由一到多个Task构成。项目结构中的每个build.gradle文件代表一个Project,在这编译脚本文件中可以定义一系列的Task,Task是由一组被顺序执行的Action对象构成。
点击Android Studio右侧的Gradle按钮,会打开一个面板,可以查看Tasks。
二、Gradle构建的生命周期
初始化阶段
Project实例会在该阶段被创建。如果一个项目有多个模块,并且每一个模块都有其对应的build.gradle文件,那么就会创建多个Project实例。
配置阶段
在该阶段,构建脚本会被执行,并为每个Project实例创建和配置Task。
执行阶段
在该阶段,Gradle将决定哪个Task会被执行。哪些Task被执行取决于开始该次构建的参数配置和该Gradle文件的当前目录。
三、Android Gradle插件主要流程
项目添加Android Gradle插件依赖:
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' //Android Gradle插件提供了构建和测试应用所需要的一切
}
准备工作
1.检查插件版本。
2.检查module是否重名。
3.初始化插件信息。
配置项目
1.检查Gradle版本是否匹配。
2.创建AndroidBuilder和DataBindingBuilder。
3.引入java plugin和jacoco plugin。
4.设置构建完成以后的缓存清理工作。
配置Extension
1.创建AppExtension,也就是build.gradle里用到的android {}dsl。
2.建依赖管理、ndk管理、任务管理、variant管理。
3.注册新增配置的回调函数,包括signingConfig、buildType、productFlavor。
4.创建默认的debug签名,创建debug和release两个buildType。
创建不依赖flavor的Task
主要创建Task:
uninstallAll,deviceCheck,connectedCheck,preBuild,extractProguardFiles,sourceSets,assembleAndroidTest,compileLint,lint,lintChecks,cleanBuildCacheresolveConfigAttr,consumeConfigAttr。
这些Task都是不需要依赖flavor数据的公共Task。
创建构建Task
1.populateVariantDataList
在方法里,会先根据flavor和dimension创建对应的组合,存放在flavorComboList里,之后调用createVariantDataForProductFlavors创建对应的VariantData。VariantData里保存了很多Task,下一步就要创建这些Task。
2.createTasksForVariantData
创建完variant数据,就要给每个variantData创建对应的Task,对应的Task有assembleXXXTask,prebuildXXX,generateXXXSource,generateXXXResources,generateXXXAssets,processXXXManifest等。
四、Groovy
概念
Groovy是Apache旗下的一种基于JVM的面向对象编程语言,既可以用于面向对象编程,也可以用作纯粹的脚本语言。Groovy与Java可以很好的互相调用并结合编程,比如在写Groovy的时候忘记了语法可以直接按Java的语法继续写,也可以在Java中调用Groovy脚本。比起Java,Groovy语法更加的灵活和简洁,可以用更少的代码来实现Java实现的同样功能。
编写与调试
Groovy的代码可以在Android Studio中进行编写和调试。具体的操作步骤是:在一个目录中新建build.gradle文件,在build.gradle中新建一个Task,在Task中编写Groovy代码,用命令行进入这个build.gradle文件所在的目录,运行gradlew Task名称 等命令行对代码进行调试。
变量
Groovy中用def关键字来定义变量,可以不指定变量的类型,默认访问修饰符是public。
def a = 1;
def int b = 1;
def c = "hello world";
方法
方法使用返回类型或def关键字定义,方法可以接收任意数量的参数,这些参数可以不申明类型,如果不提供可见性修饰符,则该方法为public。
用def关键字定义方法:
//build.gradle
task method <<{
add (1,2)
minus 1,2 //方法调用时括号可以省略
}
def add(int a,int b) {
println a+b //语句后面的分号可以省略
}
def minus(a,b) { //方法定义时参数类型可以省略
println a-b
}
//运行
在Terminal中输入:gradlew -q method
//输出
3
-1
如果指定了方法返回类型,可以不需要def关键字来定义方法:
//build.gradle
task method <<{
def number=minus 1,2
println number
}
int minus(a,b) {
return a-b
}
//运行
在Terminal中输入:gradlew -q method
//输出
-1
如果不使用return,方法的返回值为最后一行代码的执行结果:
//build.gradle
task method <<{
def number=minus 1,2
println number
}
int minus(a,b) {
a-b //return可以省略
}
//运行
在Terminal中输入:gradlew -q method
//输出
-1
Groovy中有很多省略的地方:
- 语句后面的分号可以省略。
- 方法调用时括号可以省略。
- 方法定义时参数类型可以省略。
- return可以省略。
类
Groovy类非常类似于Java类,Groovy类与Java类有以下区别:
- 默认类的修饰符为public。
- 没有可见性修饰符的字段会自动生成对应的setter和getter方法。
- 类不需要与它的源文件有相同的名称,但还是建议采用相同的名称。
//build.gradle
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
}
}
//运行
在Terminal中输入:gradlew -q method
//输出
15
语句
断言
Groovy断言和Java断言不同,它一直处于开启状态,是进行单元测试的首选方式。当断言的条件为false时,程序会抛出异常,不再执行下面的代码,从输出可以很清晰的看到发生错误的地方。
//build.gradle
task method <<{
assert 1+2 == 6
}
//运行
在Terminal中输入:gradlew -q method
//输出
Execution failed for task ':app:method'.
> assert 1+2 == 6
| |
3 false
for循环
Groovy支持Java的for(int i=0;i<N;i++)和for(int i :array)形式的循环语句,另外还支持for in loop形式,支持遍历范围、列表、Map、数组和字符串等多种类型。
//遍历范围
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]
def x = 0
for ( v in map.values() ) {
x += v
}
assert x == 6
switch语句
Groovy中的Switch语句不仅兼容Java代码,还可以处理更多类型的case表达式。case表达式可以是字符串、列表、范围、Integer等
task method <<{
def x = 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"
}
println result
assert result == "range"
}
数据类型
Groovy中的数据类型主要有以下几种:
- Java中的基本数据类型
- Groovy中的容器类
- 闭包
字符串
在Groovy种有两种字符串类型,普通字符串String(java.lang.String)和插值字符串GString(groovy.lang.GString)。
单引号字符串:
在Groovy中单引号字符串和双引号字符串都可以定义一个字符串常量,只不过单引号字符串不支持插值。
//build.gradle
def name = 'Android高级工程师'
task method <<{
println name
}
//运行
在Terminal中输入:gradlew -q method
//输出
Android高级工程师
双引号字符串:
双引号字符串支持插值,插值指的是替换字符串中的占位符,占位符表达式为为前缀。
//build.gradle
def name = 'Android高级工程师'
task method <<{
println "hello ${name}"
println "hello $name"
}
//运行
在Terminal中输入:gradlew -q method
//输出
hello Android高级工程师
hello Android高级工程师
三引号字符串:
三引号字符串可以保留文本的换行和缩进格式,不支持插值。
//build.gradle
def name = '''Android高级工程师
1Tab
2Tab'''
task method <<{
println name
}
//运行
在Terminal中输入:gradlew -q method
//输出
Android高级工程师
1Tab
2Tab
GString:
String是不可变的,GString却是可变的,GString和String即使有相同的字面量,它们的hashCodes的值也可能不同,因此应该避免使用使用GString作为Map的key。
//build.gradle
task method <<{
println "one: ${1}".hashCode() //当双引号字符串中包含插值表达式时,字符串类型为GString
println "one: 1".hashCode() //String类型
}
//运行
在Terminal中输入:gradlew -q method
//输出
-1012478518
-1012478555
List
Groovy没有定义自己的集合类,它在Java集合类的基础上进行了增强和简化。Groovy的List对应Java中的List接口,默认的实现类为Java中的ArrayList。
//build.gradle
task method <<{
def number = [1, 2, 3]
assert number instanceof List
def linkedList = [1, 2, 3] as LinkedList //使用as操作符来显式指定List的实现类为java.util.LinkedList
assert linkedList instanceof java.util.LinkedList
}
task method <<{
def number = [1, 2, 3, 4]
println number [1] //1表示列表索引为1的元素(索引从0开始算起)
println number [-1] //-1表示列表末尾的第一个元素
number << 5 //<<运算符表示在列表末尾追加一个元素
println number [4]
println number [-1]
}
//运行
在Terminal中输入:gradlew -q method
//输出
2
4
5
5
Map
创建Map同样使用[],需要同时指定键和值,默认的实现类为java.util.LinkedHashMap。
//build.gradle
task method <<{
def name = [one: 'Android', two: 'Java', three: 'Other']
println name['one']
println name.two
}
//运行
在Terminal中输入:gradlew -q method
//输出
Android
Java
task method <<{
def key = 'name'
def person = [key: 'Android'] //键值是key这个字符串,而不是key变量的值name
println person.containsKey('key')
person = [(key): 'Java'] //键值是key变量的值name,而不是key这个字符串
println person.containsKey('name')
}
//运行
在Terminal中输入:gradlew -q method
//输出
true
true
闭包
Groovy中的闭包是一个开放的、匿名的、可以接受参数和返回值的代码块。
定义闭包:
{ [closureParameters -> ] statements }
闭包分为两个部分,分别是参数列表部分[closureParameters -> ]和语句部分 statements 。参数列表部分是可选的,如果闭包只有一个参数,参数名是可选的,Groovy会隐式指定it作为参数名。
{ String a, String b ->
println "${a} is a ${b}"
}
{ println it } //隐式指定it作为参数名
{ it -> println it } //it是一个显示参数
闭包赋值:
闭包是groovy.lang.Cloush类的一个实例,这使得闭包可以赋值给变量或字段。
//将闭包赋值给一个变量
def println ={ it -> println it }
assert println instanceof Closure
//将闭包赋值给Closure类型变量
Closure do= { println 'do!' }
调用闭包:
闭包既可以当做方法来调用,也可以显示调用call方法。
//build.gradle
task method <<{
def code = { 123 }
println code() //闭包当做方法调用
println code.call() //显示调用call方法
}
//运行
在Terminal中输入:gradlew -q method
//输出
123
123
task method <<{
def code = { int i -> i%2 }
println code(3) //调用带参数的闭包
println code.call(3) //调用带参数的闭包
}
//运行
在Terminal中输入:gradlew -q method
//输出
1
1
I/O 操作
Groovy的 I/O 操作要比Java的更为的简洁。
文件读取
//build.gradle
task method <<{
def filePath = "D:/test.txt"
def file = new File(filePath)
file.eachLine {
println it
}
}
//运行
在Terminal中输入:gradlew -q method
//输出
hello
Android
Java
task method <<{
def filePath = "D:/test.txt"
def file = new File(filePath)
println file.text
}
//运行
在Terminal中输入:gradlew -q method
//输出
hello
Android
Java
文件写入
//build.gradle
task method <<{
def filePath = "D:/test.txt"
def file = new File(filePath)
file.withPrintWriter {
it.println("hello2")
it.println("Android2")
it.println("Java2")
}
}
//运行
在Terminal中输入:gradlew -q method
//输出(test.txt)
hello2
Android2
Java2
其它
asType
asType可以用于数据类型转换。
task method <<{
String a = '123'
int b = a as int
def c = a.asType(Integer)
assert b instanceof java.lang.Integer //true
assert c instanceof java.lang.Integer //true
}
判断是否为真
task method <<{
if (name != null && name.length > 0) { //true
}
}
task method <<{
if (name) {
}
}
安全取值
在Java中,要安全获取某个对象的值可能需要大量的if语句来判空,例如:
if (school != null) {
if (school.getStudent() != null) {
if (school.getStudent().getName() != null) {
System.out.println(school.getStudent().getName());
}
}
}
在Groovy中可以使用?.来安全的取值,例如:
println school?.student?.name
with操作符
对同一个对象的属性进行赋值时,可以这么做:
task method <<{
Person p = new Person()
p.name = "Android"
p.age = 10
println p.name + " " + p.age
}
class Person {
String name
Integer age
}
使用with操作符进行简化:
task method <<{
Person p = new Person()
p.with {
name = "Android"
age= 10
}
println p.name + " " + p.age
}
class Person {
String name
Integer age
}
五、Task基础
创建Task
方式1:doLast
//build.gradle
task hello {
doLast {
println 'Hello world!'
}
}
//运行
在Terminal中输入:gradlew -q hello
//输出
Hello world!
方式2:操作符<<是doLast方法的快捷版本
//build.gradle
task hello << {
println 'Hello world!'
}
//运行
在Terminal中输入:gradlew -q hello
//输出
Hello world!
方式3:用任务名称创建
//build.gradle
def Task hello=task(hello)
hello.doLast{
println "hello world"
}
//运行
在Terminal中输入:gradlew -q hello
//输出
Hello world!
方式4:用任务名称与任务配置创建
//build.gradle
def Task hello=task(hello,group:BasePlugin.BUILD_GROUP)
hello.doLast{
println "hello world"
}
//运行
在Terminal中输入:gradlew -q hello
//输出
Hello world!
方式5:用TaskContainer的create方法创建
//build.gradle
tasks.create(name: 'hello') << {
println "hello world"
}
//运行
在Terminal中输入:gradlew -q hello
//输出
Hello world!
Task依赖
//build.gradle
task hello << {
println 'Hello world!'
}
task go(dependsOn: hello) << {
println "go for it"
}
//运行
在Terminal中输入:gradlew -q go
//输出
Hello world!
go for it
动态定义Task
//build.gradle
3.times {number ->
task "task$number" << {
println "task $number"
}
}
//运行
在Terminal中输入:gradlew -q task2
//输出
task 2
times是Groovy在java.lang.Number中拓展的方法,是一个定时器。3.times中循环创建了三个新任务,隐式变量number的值为0,1,2,任务的名称由task加上number的值组成,达到了动态定义任务的目的。
Task的分组和描述
//build.gradle
task hello {
group = 'build'
description = 'hello world'
doLast {
println "任务分组: ${group}"
println "任务描述: ${description}"
}
}
task go(dependsOn: hello) << {
println "go for it"
}
//运行
在Terminal中输入:gradlew -q go
//输出
任务分组: build
任务描述: hello world
go for it
def Task hello=task(hello)
hello.description ='hello world'
hello.group=BasePlugin.BUILD_GROUP
hello.doLast{
println "任务分组: ${group}"
println "任务描述: ${description}"
}
task go(dependsOn: hello) << {
println "go for it"
}
//运行
在Terminal中输入:gradlew -q go
//输出
任务分组: build
任务描述: hello world
go for it
Task的一些重要方法
Task行为:
Task.doFirst
Task.doLast
Task依赖顺序:
Task.dependsOn
Task.mustRunAfter
Task.shouldRunAfter
Task.finalizedBy
Task的分组描述:
Task.group
Task.description
Task是否可用:
Task.enabled
Task输入输出:
Task.inputs
Task.outputs
Task是否执行:
Task.upToDateWhen
Gradle日志级别
配置如下:
无日志选项:LIFECYCLE及更高级别
-q或者--quiet:QUIET及更高级别
-i或者--info:INFO及更高级别
-d或者--debug:DEBUG及更高级别,这一般会输出所有日志
gradlew -q hello
gradlew --quiet hello
Gradle输出错误堆栈信息
在使用Gradle构建的时候,难免会有这样或者那样的问题导致构建失败,这时就需要根据日志分析解决问题。除了日志信息之外,Gradle还提供了堆栈信息的打印,能帮助我们很好地定位和分析问题。默认情况下,堆栈信息的输出是关闭的,需要我们通过命令行的堆栈信息开关打开它,这样在我们构建失败的时候,Gradle才会输出错误堆栈信息。
配置如下:
无选项:没有堆栈信息输出
-s 或者 --stacktrace:输出关键性的堆栈信息
-S 或者 --full-stacktrace:输出全部堆栈信息
一般推荐使用-s而不是-S,因为-S输出的堆栈太多太长,非常不好看,而-s反而比较精简,可以定位解决我们大部分的问题。
自己使用日志信息调试
在编写Gradle脚本的过程中,我们有时候需要输出一些日志,来验证我们的逻辑或者一些变量的值是否正确,这时候我们就可以使用Gradle提供的日志功能。
通常情况下我们都是使用print系列方法,把日志信息输出到标准的控制台输出流(它被Gradle定向为QUIET级别日志):
task method <<{
println "输出日志进行验证"
}
除了print系列方法之外,也可以使用内置的logger(实际上是调用Project的getLogger()方法获取的Logger对象实例)更灵活地控制输出不同级别的日志信息。
task method <<{
logger.quiet('quiet日志信息.')
logger.error('error日志信息.')
logger.warn('warn日志信息.')
logger.lifecycle('lifecycle日志信息.')
logger.info('info日志信息.')
logger.debug('debug日志信息.')
}
Gradle命令行
gradlew -q 任务名称:运行一个指定的任务
gradlew -q tasks:获取所有的任务信息,默认情况下只会显示那些被分组的任务的名称和描述
gradlew -q 任务名称1 -x 任务名称2:运行任务1,排除任务2
gradlew -q help --task 任务名称:获取任务帮助信息
gradlew -q 任务名称1 -q 任务名称2:先运行任务1,再运行任务2
gradlew -q hello
gradlew -q tasks
gradlew -q go -x hello
gradlew -q help --task hello
gradlew -q helloWorld -q goForit
六、Gradle项目分析
settings.gradle
settings.gradle是负责配置项目的脚本,对应Settings类,Gradle构建过程中,会根据settings.gradle生成Settings对象。
settings.gradle:
include ':app', ':app2'
rootproject/build.gradle
build.gradle负责整体项目的一些配置,对应的是Project类。Gradle构建过程中,会根据build.gradle生成Project对象(Project其实是一个接口,真正的实现类是DefaultProject),所以在build.gradle里写的DSL,其实都是Project接口的一些方法。
主要方法:
- buildscript:配置脚本的classpath
- allprojects:配置项目及其子项目
- repositories:配置仓库地址,后面的依赖都会到这里的地址进行查找
- dependencies:配置项目的依赖
build.gradle(project):
buildscript { //配置项目的classpath
repositories { //项目的仓库地址,会按顺序依次查找
maven {
name 'AliYun Google Repository Proxy'
url 'https://maven.aliyun.com/repository/google'
}
maven {
name 'AliYun Jcenter Repository Proxy'
url 'https://maven.aliyun.com/repository/jcenter'
}
}
dependencies { //项目的依赖
classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects { //子项目的配置
repositories {
maven {
name 'AliYun Google Repository Proxy'
url 'https://maven.aliyun.com/repository/google'
}
maven {
name 'AliYun Jcenter Repository Proxy'
url 'https://maven.aliyun.com/repository/jcenter'
}
}
}
ext { //定制项目属性
compileSdkVersion = 28 //可以在module的build.gradle进行引用:compileSdkVersion rootProject.ext.compileSdkVersion
}
module/build.gradle
build.gradle是子项目的配置,对应的也是Project类。子项目和根项目的配置是差不多的,不过在子项目里可以看到有一个明显的区别,就是引用了一个插件:apply plugin "com.android.application",后面的Android DSL就是application插件的extension。
主要方法:
- compileSdkVersion:指定编译需要的sdk版本
- defaultConfig:指定默认的属性,会运用到所有的variants上
- buildTypes:一些编译属性可以在这里配置
- productFlavor:配置项目的flavor
build.gradle(module):
//com.android.application 表示Android应用模块插件
//com.android.library 表示Android依赖模块插件
//插件用于扩展Gradle构建脚本的能力。在一个项目中应用一个插件,该项目就可以使用该插件预定义的一些属性和任务。
apply plugin: 'com.android.application'
android { //配置Android Gradle插件需要的内容
compileSdkVersion 28 //用来编译应用的Android API版本
//buildToolsVersion '28.0.3'
//buildToolsVersion是构建工具和编译器使用的版本号。构建工具包含命令行应用,如aapt、zipalign、dx和renderscript,这些都被用来在打包应用时生成各种中间产物。
//Android Gradle插件的版本在3.x之后默认所有的项目都使用相同的buildToolsVersion,不需要用户单独指定项目buildToolsVersion
defaultConfig { //配置应用的核心属性
applicationId "com.tomorrow.architetest"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
resConfigs 'en', 'zh-rCN' ,'es', 'hdpi' //过滤语言、过滤drawable文件夹资源
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes { //定义如何构建和打包不同构建类型的应用
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions { //指定Java版本
sourceCompatibility 1.8
targetCompatibility 1.8
}
lintOptions { //link检测
abortOnError false
}
//flavor相关配置
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
}
//项目需要的依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) //jar包依赖
implementation 'com.android.support:appcompat-v7:28.0.0' //远程仓库依赖
implementation 'com.android.support:support-compat:28.0.0'
implementation project(':module1') // 项目依赖
}
七、Android Task
在新建的Android项目中,其会包括Android tasks、build tasks、build setup tasks、help tasks、install tasks、verification tasks和其它tasks。
基础Task
Gradle的Android插件使用了Java基础插件,而Java基础插件又使用了基础插件,基础插件添加了Task的标准生命周期和一些共同约定的属性。基础插件定义了assemble和clean tasks,Java插件定义了check和build tasks。
这些Task的约定如下:
- assemble:集合项目的输出。
- clean:清理项目的输出。
- check:运行所有检查,通常是单元测试和集成测试。
- build:同时运行assemble和check。
Android Task
Android插件扩展了基础Task,并实现了它们的行为:
- assemble:为每个构建版本创建一个APK。
- clean:删除所有的构建内容,例如APK文件。
- check:运行Link检查,如果Link发现一个问题,则可终止构建。
- build:同时运行assemble和check。
assemble Task默认依赖于assembleDebug和assembleRelease,如果添加了更多的构建类型,那么就会有更多的Task。这意味着,assemble将会触发每一个构建类型,并进行一次构建操作。
除了扩展这些Task之外,Android Gradle插件还添加了一些新的Task:
- connectedCheck:在连接设备或模拟器上运行测试。
- deviceCheck:一个占位任务,专为其他插件在远端设备上运行测试。
- installDebug和installRelease:在连接设备或模拟器上安装特定版本。
- 所有的install tasks都会有uninstall tasks。
八、依赖
依赖配置
在Gradle 3.4里引入了新的依赖配置。
例子:
- 项目里有三个模块:app、module1、module2
- 模块app中有一个类ModuleApi
- 模块module1中有一个类Module1Api
- 模块module2中有一个类Module2Api
- app依赖module1,module1依赖module2
新配置:implementation
弃用配置:compile
作用:api只会暴露给直接依赖的模块,大多数应用和测试模块都应使用此配置。
例子:当app使用implementation依赖module1、module1使用implementation依赖module2时,在app中无法引用到Module2Api类。
新配置:api
弃用配置:compile
作用:api会暴露给间接依赖的模块。
例子:当app使用implementation依赖module1、module1使用api依赖module2时,在app中可以引用到Module2Api类。
新配置:compileOnly
弃用配置:provided
作用:只在编译期间依赖模块,打包以后运行时不会依赖,可以用来解决一些库冲突的问题。
例子:当app使用implementation依赖module1、module1使用compileOnly依赖module2时,
在编译阶段,app无法引用到Module2Api类,module1可以引用到Module2Api类;
在运行阶段,module1无法引用到Module2Api类。
新配置:runtimeOnly
弃用配置:apk
作用:只在运行时依赖模块,编译时不依赖。
例子:当app使用implementation依赖module1、module1使用runtimeOnly依赖module2时,
在编译阶段,app无法引用到Module2Api类,module1无法引用到Module2Api类;
在运行阶段,module1可以引用到Module2Api类。
依赖管理
1.依赖jcenter包
每个库名称包含三个元素:组名:库名称:版本号。
implementation 'com.android.support:appcompat-v7:25.0.0'
2.依赖本地module
implementation project(':YibaAnalytics')
3.依赖jar包
把jar包放在libs目录下,在build.gradle中添加依赖。
dependencies {
implementation files('libs/YibaAnalytics5.jar')
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
4.依赖aar包
把aar包放到libs目录下,在build.gradle中添加依赖。
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
compile(name:'YibaAnalytics-release', ext:'aar')
}
5.自定义依赖包目录
当aar包需要被多module依赖时,我们就不能把aar包放在单一的module中,我们可以在项目的根目录创建一个目录,比如叫aar目录,然后把aar包放进去。
在项目的根目录的build.gradle的allprojects标签下的repositories添加:
flatDir {
dirs '../aar'
}
然后就可以添加依赖了:
compile(name:'YibaAnalytics-release', ext:'aar')
6.native包(so包)
用c或者c++写的library叫做so包,Android插件默认情况下支持native包,需要把.so文件放在jniLibs目录中,jniLibs目录应该和java目录在同一级。
九、创建构建Variant
当开发一个应用时,通常会有几个不同的版本。
构建类型
在Android Gradle插件中,构建类型通常被用来定义如何构建一个应用或依赖库。
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.tomorrow.architetest"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
当默认的设置不够用时,我们可以很容易地创建自定义构建类型。
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
staging.initWith(buildTypes.debug) //initWith创建一个新的构建类型,并且复制一个已经存在的构建类型的所有属性到新的构建类型中
staging {
applicationIdSuffix ".staging" //给applicationId添加后缀
versionNameSuffix "-staging" //给versionName添加后缀
debuggable false
}
}
存在三个构建类型:debug、release、staging。
project flavor
在Android Gradle插件3.x之后,每个flavor必须对应一个dimension,可以理解为flavor的分组,然后不同dimension里的flavor两两组合形成一个variant。
flavorDimensions "size", "color"
productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
那么生成的variant对应的就是bigBlue,bigRed,smallBlue,smallRed。
构建variant
构建variant是构建类型和product flavor结合的结果。不论什么时候创建一个构建类型或product flavor,新的variant都会被创建。
例如:
- bigBlueDebug
- bigRedDebug
- smallBlueDebug
- smallRedDebug
- bigBlueRelease
- bigRedRelease
- smallBlueRelease
- smallRedRelease
Task
Android Gradle插件会为每一个构建variant创建任务。
例如:
- assembleBigBlueDebug
- assembleBigRedDebug
- assembleSmallBlueDebug
- assembleSmallRedDebug
- assembleBigBlueRelease
- assembleBigRedRelease
- assembleSmallBlueRelease
- assembleSmallRedRelease
源集
构建variant,是一个构建类型和一个或多个product flavor的结合体,其可以有自己的源集文件夹。
例如:由debug构建类型、blue flavor和free flavor创建的variant可以有其自己的源集src/blueFreeDebug/java/。其可以通过使用sourceSets代码块来覆盖文件夹的位置。
SourceSet增加源码及资源目录
android {
sourceSets {
main {
java.srcDir 'src/test1/java' //增加代码目录
res.srcDirs 'src/test1/res' //增加资源目录
}
}
}
SourceSet定义AndroidManifest文件
android {
def appDebug = false
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
appDebug = false
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
appDebug = true
}
}
sourceSets {
main {
if (appDebug) {
manifest.srcFile 'src/test1/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
源集合并资源和manifest
Android Gradle插件在打包应用之前将main源集和构建类型源集合并在一起。此外,library项目也可以提供额外的资源,这些也需要被合并,这同样适用于manifest文件。
例如:在应用的debug variant中可能需要额外的Android权限来存储log文件,你并不想在main源集中声明该权限,因为这会吓跑潜在客户,你可以在debug构建类型的源集中额外添加一个manifest文件来声明额外的权限。
资源和manifest的优先顺序:
Build type > Flavor > Main > Dependencies
如果资源在flavor和main源集中都有声明,那么flavor中的资源将被赋予更高的优先级。在这种情况下,flavor源集中的资源将会被打包。在library项目中声明的资源通常具有最低优先级。
创建构建variant
android {
buildTypes {
debug {
buildConfigField "String", "API_URL", "\"http://test.example.com/api\"" //buildConfigField自定义属性,可以在代码中进行引用:BuildConfig.API_URL
}
staging.initWith(android.buildTypes.debug)
staging {
buildConfigField "String", "API_URL", "\"http://staging.example.com/api\""
applicationIdSuffix ".staging"
}
}
productFlavors {
red {
applicationId "com.gradleforandroid.red"
resValue "color", "flavor_color", "#ff0000"
}
blue {
applicationId "com.gradleforandroid.blue"
resValue "color", "flavor_color", "#0000ff"
}
}
}
variant过滤器
variant过滤器可以完全忽略某些variant。可以在App或library根目录下的build.gradle文件使用以下代码来过滤variants:
android.variantFilter{ variant ->
if(variant.buildType.name.equals('release')) {
variant.getFlavors().each() { flavor ->
if(flavor.name.equals('blue')) {
variant.setIgnore(true)
}
}
}
}
签名配置
只有buildTypes:
android {
signingConfigs { //实际应用中不要在构建配置文件中存储凭证,而是使用Gradle配置文件
staging.initWith(signingConfigs.debug) //staging签名配置
release { //release签名配置
storeFile file("release.keystore")
storePassword "secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
buildTypes {
release {
signingConfigs signingConfigs.release
}
}
}
有productFlavors:
android {
signingConfigs { //实际应用中不要在构建配置文件中存储凭证,而是使用Gradle配置文件
staging.initWith(signingConfigs.debug) //staging签名配置
release { //release签名配置
storeFile file("release.keystore")
storePassword "secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
buildTypes {
release {
productFlavors.red.signingConfig signingCofigs.staging
productFlavors.blue.signingConfig signingCofigs.release
}
}
productFlavors {
red {
applicationId "com.gradleforandroid.red"
resValue "color", "flavor_color", "#ff0000"
}
blue {
applicationId "com.gradleforandroid.blue"
resValue "color", "flavor_color", "#0000ff"
}
}
}
存储凭证的正确方式
1.在项目根目录创建一个名为private.properties的文件,然后添加一行代码:
//private.properties
release.password = android
2.在build.gradle定义一个名为getReleasePassword的任务:
//build.gradle
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
signingConfigs { //实际应用中不要在构建配置文件中存储凭证,而是使用Gradle配置文件
release { //release签名配置
storeFile file("debug.keystore")
keyAlias "androiddebugkey"
}
}
}
task getReleasePassword << {
println 'getReleasePassword'
def password = ''
if(rootProject.file('private.properties').exists()) {
Properties properties = new Properties()
properties.load(rootProject.file('private.properties').newDataInputStream())
password = properties.getProperty('release.password')
println 'password: ' + password
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
}
}
tasks.whenTaskAdded { theTask ->
if(theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}
自动重命名APK
//build.gradle
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
def file = output.outputFile
outputFileName = file.name.replace(".apk", "-${variant.versionName}-${getTime()}.apk")
}
}
//获取当前时间
def getTime() {
String today = new Date().format('YYYY年MM月dd日HH时mm分')
return today
}
//输出
app-debug-1.0-2019年12月12日18时36分.apk
十、统一管理依赖版本
1.在项目根目录下,新建config.gradle文件:
//config.gradle
ext {
android = [
compileSdkVersion: 28,
buildToolsVersion: "28.0.3",
applicationId: "com.tomorrow.architetest",
minSdkVersion: 15,
targetSdkVersion: 28,
versionCode: 1,
versionName: "1.0",
testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner"
]
dependencies = [
"support-v7" : 'com.android.support:appcompat-v7:28.0.0'
]
}
2.在项目根目录下的build.gradle文件,新增:
//build.gradle
apply from: "config.gradle"
3.在各Module或者Library中的build.gradle文件,进行引用:
//build.gradle
android {
...
compileSdkVersion rootProject.ext.android.compileSdkVersion
...
}
相关链接
Gradle
Gradle基本使用
Android Gradle插件主要流程
Gradle源码分析
《Gradle for Android 中文版》