参考资料:
http://gold.xitu.io/post/580c85768ac247005b5472f9
http://www.jianshu.com/p/9df3c3b6067a
http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661971&idx=1&sn=3fb69537bbc5fbb14d152ba6381c3b83#rd
https://segmentfault.com/a/1190000004229002
http://wiki.jikexueyuan.com/project/deep-android-gradle/
dex 打包
http://blog.csdn.net/mynameishuangshuai/article/details/52703029
For Android 准备的基础
构建工具
Gradle是一种构建工具,Android Studio默认采用Gradle来构建,Eclipse中使用的ant来构建Android;
构建工具指的是对项目进行编译、运行、签名、打包、依赖管理等一系列功能的合集,另外一个非常重要的功能是管理依赖(第三方库管理);
Google 开发了Andriod Studio 插件 Android Gradle Plugin 用来构建 Android App;
新增内容(基础知识)##
Gradle是一个框架,定义了自己一些规则,我们需要遵循她的设计规则:
- 在Gradle中,每一个待编译的工程都叫做一个Project,如:Android Project目录下的各种lib引入库,都是一个Project。
- 在每个Project在构建时,又包含了一系列Task,比如:Android APK的编译包含:java源代码编译Task、Android资源编译Task、签名Task等等;
- 一个Project有多少个task,由其编译脚本指定的插件决定,什么是插件?插件就是用来定义Task,并执行这些Task的东西;
Gradle负责定义流程和规则,而具体的编译工作则是通过插件的方式来完成,
如:编译Java的有java插件,编译Android lib 的有Android lib 的插件;
总之:Gradle中每一个待编译的工程,都是一个Project,而Project的编译的工作,是由其定义的一个一个Task来定义与执行的;
在Android工程中,每一个Library、每一个App都是单独的Project,同时在每一个Project的根目录下,都有一个build.gradle文件,表示Project的编译脚本;
settings.gradle文件
在Android工程中,我们一般都有多个Project,如上,每个project都有一个build.gradle文件与此对应;在工程的根目录,有一个build.gradle文件,她负责配置其他子Project的,与settings.gradle文件,settings.gradle文件指出该工程包含多少个 子 Project;
有了这2个文件,在项目的根目录进行编译时,可以把项目中的所有project都编译好;
Gradle 相关命令
- gradle projects 查看工程信息;
- gradle tasks 查看任务信息;
- gradle task-name 执行任务,如:gralde clean,gradle properties
task 的依赖关系
task和task之间可能有关系,如:某task的执行,需要其他task先执行完成,这就是依赖关系;如:assemble task就依赖其他task先执行,assemble 才能执行;
可以指定 assemble 依赖于 自己定义的 task,这样,自定义的task会优先执行;
gradle工作流程
- 初始化阶段:对于多project build而言,就是执行 settings.gradle;
- Configuration阶段:解析每个project中的build.gradle文件,在这2个阶段之间,可加入一些定制化的hook;
- 预执行阶段:现整个 build的 project及内部的task关系已确定;
- 执行任务阶段;
gradle编程模型
Gradle执行的时候,会把脚本转化成Java对象,Gradle主要3种对象,并与三种不同的脚本文件对应:
- Gradle对象:执行gradle xxx,gradle会从默认配置脚本中构造出一个Gradle对象,整个执行过程中,只有这么一个对象,类型是Gradle;
- Project对象:由build.gralde转;
- Settings对象:settings.gradle转;
Project对象
Project包含若干个Tasks,Project对应具体工程,需要为Project加载所需要的插件,如:为java工程加入Java插件;
- 加载插件 :调用apply方法, https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
apply plugin: 'com.android.library'
apply plugin: 'com.android.application'
Groovy支持函数调用时,通过 参数名1:参数值1,,参数名2:参数值2来传递参数;
// 加载自定义的插件(这里为一个工具文件)
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle" - 设置属性
gradle可能包含不止一个build.gradle文件,考虑在多个脚本中设置属性:
gradle提供名为 extra property的方法,表示额外属性,在第一次定义该属性时需通过ext前缀来标示他是一个额外的属性,后面的存在,就不需要ext前缀了,ext属性支持Project和Gradle对象,意思是为Project和Gradle对象设置ext属性;
ext {
local = 'Hello groovy'
}
task printProperties {
println local // Local extra property
if (project.hasProperty('cmd')) {
println cmd // Command line property
}
}
如果在 utils.gradle 中定义了一些函数,然后想在其他 build.gradle 中调用这些函数。那该怎么做呢?
[utils.gradle]
//utils.gradle 中定义了一个获取 AndroidManifests.xml versionName 的函数
def getVersionNameAdvanced(){
// 下面这行代码中的 project 是谁?
def xmlFile = project.file("AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest['@android:versionName']
}
//现在,想把这个 API 输出到各个 Project。由于这个 utils.gradle 会被每一个 Project Apply,所以
//我可以把 getVersionNameAdvanced 定义成一个 closure,然后赋值到一个外部属性
// 下面的 ext 是谁的 ext?
ext{ //此段花括号中代码是闭包
//除了 ext.xxx=value 这种定义方法外,还可以使用 ext{}这种书写方法。
//ext{}不是 ext(Closure)对应的函数调用。但是 ext{}中的{}确实是闭包。
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
问题
- project是谁?
当一个project apply 一个gradle文件时,这个gradle文件会转化成一个script对象;
script中有一个delegate对象,这个delegate,默认加载(即调用apply)它的project对象;
在 apply函数中,除了 from参数,还有个to参数,通过to参数,可改变delegate对象为其他;
delegate就是当在script中,操作一些不是script自己定义的变量,或者函数时,gradle会到script的delegate对象中去找,看有没有定义这些变量or函数;
==》这样project就是加载utils.gradle的Project; - ext是谁的ext?
==》 project对应的ext了;此处为 Project 添加了一些 closure。那么,在 Project 中
就可以调用 getVersionNameAdvanced 函数了
在Java和Groovy中:可能会把常用的函数放到一个辅助类中,通过import他们,并调用;
但在Gradle中,更正规的方式在 xxx.gradle中定义插件,然后通过Task的方式来完成工作;
Task介绍
task是Gradle中的一种数据类型,表示一些要执行的工作,不同的插件可添加不同task,每一个task需要和一个project关联;
Task 的 API 文档位于 https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
[build.gradle]
// Task 是和Project关联的,所以,需要利用Project的task函数来创建一个Task
task myTask // 新建task名字
task myTask {} // 闭包
task myType << { task action } // << 符号是 doLast缩写
task myTask(type:SomeType)
task myTask(type:SomeType) { }
上面都用到了Project的一个函数,task,注意:
- 一个Task包含若干action,所以 Task有doFirst和doLast二个函数,用于添加需要最先执行的Action和最后需要执行的Action,action是一个闭包;
2.Task创建的时候可指定Type,通过 type:名字表达,就是告诉gradle,这个新建的Task对象会从哪个基类Task派生,如:Copy是Gradle中的一个类,当 task myTask(type:Copy)的时候,创建的Task是一个Copy Task;
3.当使用task myTask {XXX}的时候,花括号是一个闭包,这会导致gradle在创建此task之后,返回给用户之前,会先执行了 闭包内容;
4.当使用task myTask << {XXX}的时候,创建task对象,并把closure作为一个action加到此task的action队列中,并且告诉他“最后才执行这个closure”;
Script Block
gradle文件中包含一些 Script Block,她的作用是让我们来配置相关信息的,不同的SB有不同的配置;
如:
buildscript { // 这是一个Script Block
repositories {
jcenter()
}
每个SB后面都需要跟一个花括号,闭包;
https://docs.gradle.org/current/javadoc/ ,可输入SB名字,进行查找;
解释几个SB:
- subprojects:它会遍历 工程 中的 每个子 Project,在其closure中,默认参数是子project对应的Project对象,由于其他SB都在subprojects中,所以相当于对每个Project都配置了一些信息;
- buildscript::它的 closure 是在一个类型为 ScriptHandler 的对象上执行的。主意用来所依赖的 classpa
th 等信息。通过查看 ScriptHandler API 可知,在 buildscript SB 中,你可以调用 ScriptHandler 提供
的 repositories(Closure )、dependencies(Closure)函数。这也是为什么 repositories 和 dependencies
两个 SB 为什么要放在 buildscript 的花括号中的原因。这就是所谓的行话,得知道规矩。不知道
规矩你就乱了。记不住规矩,又不知道查 SDK,那么就彻底抓瞎,只能到网上到处找答案了!
Android 自己定义了好多 ScriptBlock。Android 定义的 DSL 参考文档在
https://developer.android.com/tools/building/plugin-for-gradle.html
其他一些:##
Gradle Wrapper
参考:
http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661971&idx=1&sn=3fb69537bbc5fbb14d152ba6381c3b83#rd
我们可以在项目的根目录,输入 gradlew -v 可查看 gradle 版本;
gradlew 为 gradle wapper 的缩写
注意:如果没有配置 全局的 gralde 环境变量,在Android studio 的 命令框中,需要输入./ 再加 graldew来使用相关命令,如下:
常用命令
./gradlew -v 查看版本号 (win 下 输入 gradlew -v)
./gradlew clean 清除/app目录下的build文件夹
./gradlew build 检查依赖并编译打包
./gradlew build 命令把 debug、release 环境的包都打出来;
./gradlew assembleDebug 编译并打Debug包
./gradlew assembleRelease 编译并打Release的包
基本的Gradle
构建Android程序,需要构建脚本, gradle默认提供了一些配置与默认值,简化了我们的构建工作;
Project与Tasks
Gradle中有2个非常重要的对象,Project 和 Tasks;
注意: Android Studio 中的project和Gradle中的project不是同一个概念。
这里的project指的是 gradle中的project;
每个project有至少一个tasks,每个build.gradle文件代表一个project,tasks在build.gradle中定义,一个tasks包含了多个动作,然后按顺序一个一个执行,类似java中的方法;
构建生命周期
一旦一个tasks被执行,后面将不再执行,不包含依赖的tasks总是优先执行,一个构建会经历以下3个阶段:
- 初始化阶段:project实例在这儿创建,如果有多个模块,即有多个build.gradle文件,多个project将会被创建;
- 配置阶段:在该阶段,build.gradle脚本将会执行,为每个project创建和配置所有的tasks;
- 执行阶段:这一阶段,gradle会决定哪一个tasks会被执行,哪一个tasks会被执行完全依赖开始构建时传入的参数和当前所在的文件夹位置有关
build.gradle的配置文件
基于gradle构建的项目,至少有一个 build.gradle文件,下面的是Android的build.gradle:
这个 就是 实际构建开始的地方
// 定义全局的相关属性,使用 jcenter作为仓库
buildscript {
repositories {
jcenter()
}
// 定义构建过程
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
}
}
// 用来定义各个模块的默认属性,在所有模块中的可见
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
构建脚本定义了Android构建工具,还有Android的依赖库
// 每个app都需要这个插件,Android plugin 提供了所有需要去构建和测试的应用
apply plugin: 'com.android.application'
// 表示的是一个 依赖库
apply plugin: 'com.android.library'
基本的Tasks
android插件依赖于Java插件,java依赖于base插件,
base插件有基本的tasks生命周期和一些通用的属性;
base插件定义了例如assemble和clean任务,Java插件定义了check和build任务,这两个任务不在base插件中定义。
这些tasks的约定含义:
assemble: 集合所有的output
clean: 清除所有的output
check: 执行所有的checks检查,通常是unit测试和instrumentation测试
build: 执行所有的assemble和check
Java插件同时也添加了source sets的概念。
Android Tasks
Android插件继承了基本tasks,并实现了自己的行为:
- assemble 针对每个版本创建一个apk
- clean 删除所有的构建任务,包含apk文件
- check 执行Lint检查并且能够在Lint检测到错误后停止执行脚本
- build 执行assemble和check
默认情况下assemble tasks定义了assembleDebug和assembleRelease,当然你还可以定义更多构建版本。除了这些tasks,android 插件也提供了一些新的tasks:
- connectedCheck 在测试机上执行所有测试任务
- deviceCheck 执行所有的测试在远程设备上
- installDebug和installRelease 在设备上安装一个特殊的版本
- 所有的install task对应有uninstall 任务
Android Studio中的tasks
![A$PZHC}F`6YTR30Q~JH]{NM.png](http://upload-images.jianshu.io/upload_images/2003670-088f7832be095f82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
依赖管理
gradle自动为android程序添加了仓库,默认是jcenter,我们添加的某个第三方jar,称为一个依赖,比如:support v7包,gson 等;
一个依赖需要定义3个元素:group,name和version,添加依赖使用的是 groovy 语法,如下:
// 依赖
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
依赖的配置
有些时候,你可能需要和sdk协调工作。为了能顺利编译你的代码,你需要添加SDK到你的编译环境。你不需要将sdk包含在你的APK中,因为它早已经存在于设备中,所以配置来啦,我们会有5个不同的配置:
- compile
- apk
- provided
- testCompile
- androidTestCompile
compile 默认,其含义是包含所有的依赖包,即在APK里,compile的依赖会存在。
apk 的意思是apk中存在,但是不会加入编译中,这个貌似用的比较少。
provided 的意思是提供编译支持,但是不会写入apk。
testCompile 和androidTestCompile 会添加额外的library支持针对测试。
这些配置将会被用在测试相关的tasks中,这会对添加测试框架例如JUnit或者Espresso非常有用,因为你只是想让这些框架们能够出现在测试apk中,而不是生产apk中。
构建版本
一个app如果有多个版本,比如 release,debug,不同渠道不同版本等,使用 gralde 可以方便管理这些;
- Build types;
- Product flavors;
- Build variants
- Signing configurations;
buildTypes
gradle 的android插件中,一个版本构建意味着一个app或者依赖库如何被构建,每个构建版本可能有一些特殊面,比如 是否 debug,application id,应用名称,哪些资源是否需要删掉等,可以定义一个构建版本 buildTypes 方法,如:
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
product flavors
product flavors 用来为一个app创建不同的版本,比如:app的付费与免费; 如果 app 需要对内对外 完全隔离,就可以使用 product flavors,
// 多渠道打包
productFlavors {
// 个性化定制
xiaomi {
applicationId "groovy.better.com.groovytest.xiaomi"
minSdkVersion 11
}
huawei {
applicationId "groovy.better.com.groovytest.huawei"
minSdkVersion 14
}
baidu {
applicationId "groovy.better.com.groovytest.baidu"
minSdkVersion 16
}
}
// apk名称修改
applicationVariants.all { variant ->
if (variant.buildType.name.equals('release')) {
variant.outputs.each { output ->
def appName = 'demo'
def oldFile = output.outputFile
def buildName
def releaseApkName
variant.productFlavors.each { product ->
buildName = product.name
}
releaseApkName = appName + getVersionByMainfest() + '_' + buildName + '_' + getNowTime() + '.apk'
output.outputFile = new File(oldFile.parent, releaseApkName)
}
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
// -----> 自定义的方法
//获取时间戳
def getNowTime() {
def date = new Date()
def now = date.format('yyyyMMddHHmm')
return now
}
//从androidManifest.xml中获取版本号
def getVersionByMainfest() {
def parser = new com.android.builder.core.DefaultManifestParser()
return parser.getVersionName(android.sourceSets.main.manifest.srcFile)
}
BuildConfig配置
BuildConfig.java文件,无法进行动态配置,她是通过 module 相应的gradle文件生成的,可通过 module的 gralde文件,进行一些全局的开关控制:
添加配置代码:
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField 'String', 'TEST_NAME', '"test_debug"' // ildConfigField
resValue "string", "test_name", "test_debug" //resValue
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField 'String', 'TEST_NAME', '"test_release"' // ildConfigField
resValue "string", "test_name", "test_release" //resValue
}
}
生成的 BuildConfig.java 文件:
- buildConfigField: 会根据gradle的配置,在原来默认的BuildConfig.java基础上,动态添加一个指定数据类型的value。
buildConfigField 一共有3个参数,具体参考上面的参考资料; - resValue: buildConfigField主要改变了java常量,Gradle组件提供了resValue字段,用于动态生成value资源,在程序中也可以访问到,其中生成的目标存在generated.xml中,使用的规则与buildConfigField 类似,即类型+常量名+常量值,如上代码 resValue 部分,生成generated.xml截图:
** 使用占位符动态配置 清单文件中 :meta-data **
请注意:占位符须与 gradle中的 名称一致;
清单文件代码:
配置不同渠道上的值:
构建的生命周期
初始化阶段:gralde寻找 settings.gradle文件,如果项目有多个模块,settings.gralde文件定义了模块的位置,如果这些子目录包含其自己的 build.gradle文件,gradle将运行其,并将它们合并到构建任务中;