1,初识
Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。
2,工作流程
Gradle工作包含三个阶段:
- 首先是初始化阶段。对我们前面的multi-project build而言,就是执行settings.gradle
- Configration阶段的目标是解析每个project中的build.gradle。
解析完成后,其内部的Task会被添加到一个有向图中,用于解决Task的依赖关系。
这个阶段完成以后,整个build中的Project与内部Task的关系也映射完毕。 - 最后一个阶段就是执行任务了。根据所有配置完毕的Task来执行构建过程。
3,Gradle对象
Gradle主要有三种对象,这三种对象对应三种不同的脚本文件,在gradle执行的时候,会将脚本转换成对应的对象:
- Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
- Project对象:每一个build.gradle会转换成一个Project对象。
- Settings对象:每一个settings.gradle都会转换成一个Settings对象。
注意,对于其他 gradle 文件,除非定义了 class,否则会转换成一个实现了 Script 接口的对象。
当我们执行 gradle 的时候,gradle 首先是按顺序解析各个 gradle 文件。这里边就有所谓的生命周期的问题,即先解析谁,后解析谁。
构建的生命周期, ,然后根据Settings中的配置,创建Project对象,,根据文件内容来对project对象进行配置,
3.1 gradle 的用法
3.1.1 定义依赖
group、name、version
compile group:'com.android.support.constraint',name:'constraint-layout',version:'1.0.2'
3.1.2 扩展属性
setting.gradle
ext {
android = [compileSdkVersion: 25,
buildToolsVersion: "25.0.1",
applicationId : "com.dotc.trendyvip",
minSdkVersion : 15,
targetSdkVersion : 21,
versionCode : 3]
dependencies = [
appcompatv7 : 'com.android.support:appcompat-v7:25.0.1',
supportv4 : 'com.android.support:support-v4:25.0.1',
cardview : 'com.android.support:cardview-v7:25.0.1',
design : 'com.android.support:design:25.0.1',
recyclerview : 'com.android.support:recyclerview-v7:25.0.1',
eventbus : 'org.greenrobot:eventbus:3.1.1',
gson : 'com.google.code.gson:gson:2.7',
firebase : 'com.google.firebase:firebase-messaging:10.0.1',
butterknife : 'com.jakewharton:butterknife:8.8.1',
annotationProcessor: 'com.jakewharton:butterknife-compiler:8.8.1',
volley : 'com.mcxiaoke.volley:library:1.0.19',
bugly : 'com.tencent.bugly:crashreport:latest.release',
analyticssdk : 'com.dotc.sdk:analytics-lite:1.0.2.0110.1502',
greendao : 'org.greenrobot:greendao:3.2.2',
glide : 'com.github.bumptech.glide:glide:3.7.0',
glidevolley : 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
]
}
build.gradle
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile rootProject.ext.dependencies.appcompatv7
compile rootProject.ext.dependencies.supportv4
compile rootProject.ext.dependencies.design
compile rootProject.ext.dependencies.recyclerview
compile rootProject.ext.dependencies.cardview
compile rootProject.ext.dependencies.eventbus
compile rootProject.ext.dependencies.gson
// compile rootProject.ext.dependencies.butterknife
// annotationProcessor rootProject.ext.dependencies.annotationProcessor
compile rootProject.ext.dependencies.bugly
compile rootProject.ext.dependencies.analyticssdk
compile rootProject.ext.dependencies.greendao
// glide
compile rootProject.ext.dependencies.glide
compile rootProject.ext.dependencies.glidevolley
compile rootProject.ext.dependencies.volley
compile rootProject.ext.dependencies.firebase
compile 'com.android.support.constraint:constraint-layout:1.0.2'
}
3.1.3 更改打包名
applicationVariants.all { variant ->
variant.outputs.each { output ->
if (output.zipAlign) {
def file = output.outputFile
def flavor = variant.productFlavors[0].name;
def fileName = file.name.replace("java-" + flavor + "-release.apk", "ime" + "-" + flavor + "-v" + "${variant.mergedFlavor.versionName}" + "-r" + svnRevision() + ".apk")
output.outputFile = new File(file.parent, fileName)
}
def file = output.packageApplication.outputFile
def flavor = variant.productFlavors[0].name;
def fileName = file.name.replace("java-" + flavor + "-release.apk", "ime" + "-" + flavor + "-v" + "${variant.mergedFlavor.versionName}" + "-r" + svnRevision() + ".apk")
output.packageApplication.outputFile = new File(file.parent, fileName)
}
}
3.1.4.Excluding Dependencies
compile ('com.dotc.sdk:allinonesdk:3.0.0.0602.578') {
exclude group:'com.dotc.sdk', module: 'adsdk'
exclude group:'com.dotc.sdk', module: 'innersdk'
}
3.1.5.productFlavors
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
3.1.6.apk签名
signingConfigs {
releaseSignConfig {
storeFile file('./trendyvip.key')
storePassword 'trendyvip'
keyAlias 'trendyvip'
keyPassword 'trendyvip'
}
}
3.1.7.sourceSets
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src', 'gen-javame', 'thrift']
resources.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src']
aidl.srcDirs = ['aidl']
renderscript.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs'] //自动编译lib下面的jar包
}
dev.res.srcDirs = ['flavors/flash/res']
dev.assets.srcDirs = ['flavors/flash/assets']
play.res.srcDirs = ['flavors/flash/res']
play.assets.srcDirs = ['flavors/flash/assets']
tap_dev.res.srcDirs = ['flavors/tap/res']
tap_dev.assets.srcDirs = ['flavors/tap/assets']
tap_play.res.srcDirs = ['flavors/tap/res']
tap_play.assets.srcDirs = ['flavors/tap/assets']
}
}
3.1.8.引用aar
repositories {
flatDir {
dirs 'libs'
}
}
// 具体引用:
compile(name: 'facebook-android-sdk-4.5.0', ext: 'aar')
4,Task简介
一个project中Task的数量,是由编译脚本制定的插件决定。插件是什么呢?插件就是用来定义 Task,并具体执行这些 Task 的东西。
Gradle 是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译 Java 有 Java 插件,编译 Groovy 有 Groovy 插件,编译 Android APP 有 Android APP 插件,编译 Android Library 有 Android Library 插件
apply plugin: 'com.android.library' <== 如果是编译 Library,则加载此插件
apply plugin: 'com.android.application' <== 如果是编译 Android APP,则加载此插件
除了加载二进制的插件(jar 包),还可以加载 gradle 文件,如
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
5,Groovy入门
一个Task包含若干Action。Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要最后执行的Action。Action就是一个闭包。对于原有的Task,我们可以在其执行之前或者执行之后,进行一系列的Hook操作,在其执行之前和执行之后,添加一些操作。例:
tasks.getByName("task"){
it.doLast{
println "do the task"
}
}
Java程序员可以无缝切换到使用Groovy开发程序。Groovy内部会将其编译成Java class然后启动虚拟机来执行。
Groovy调用Java
groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该 Java 类
Java调用Groovy
1)方法1:直接调用
IDEA/Eclipse 开发环境下,通过安装相应的 groovy 解释器插件,既可以在 Java 代码中直接调用 groovy,在这种方式下,编译器会自动将groovy类编译为class后,再由该Java类调用
public class Test {
public static void main(String[] args){
GroovyDemo demo = new GroovyDemo("assad");
System.out.println(demo.sayHello());
}
}
2)方法二:反射动态调用
通过反射的方式调用groovy类,当groovy脚本修改后,无需重新编译,自动执行,实现groovy脚本的动态调用
public class Test {
public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
//获取 groovy 类的反射对象
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("demo/GroovyDemo.groovy"));
//实例化 GroovyDemo 类
GroovyObject groovyObj = (GroovyObject) groovyClass.newInstance("assad");
//调用 groovyDemo 成员方法
System.out.println( groovyObj.invokeMethod("sayHello",null));
}
}
6,Groovy闭包
闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:
def aClosure = {// 闭包是一段代码,所以需要用花括号括起来..
Stringparam1, int param2 -> // 这个箭头很关键。箭头前面是参数定义,箭头后面是代码
println"this is code" // 这是代码,最后一句是返回值,
// 也可以使用 return,和 Groovy 中普通函数一样
}
简而言之,Closure 的定义格式是:
def xxx = {paramters -> code} // 或者
def xxx = {无参数,纯 code} 这种 case 不需要 -> 符号
说实话,从 C/C++ 语言的角度看,闭包和函数指针很像。闭包定义好后,要调用它的方法就是:
闭包对象.call(参数)
或者更像函数指针调用的方法:
闭包对象 (参数)
比如:
aClosure.call("this is string",100)
// 或者
aClosure("this is string", 100)
如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。比如:
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
// 等同于
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
注意:
- Groovy 中,当函数的最后一个参数是闭包的话,可以省略圆括号。
- Closure 的参数需要查文档
7,Gradle 配置定义
配置属性
Gradle 提供了一种名为extra property的方法。extra property是额外属性的意思,在第一次定义该属性的时候需要通过 ext 前缀来标示它是一个额外的属性。定义好之后,后面的存取就不需要 ext 前缀了。ext 属性支持 Project 和 Gradle 对象。即 Project 和 Gradle 对象都可以设置 ext 属性,如:
def initMinshengGradleEnvironment(){
// 属性值从 local.properites 中读取
Properties properties = new Properties()
File propertyFile = new File(rootDir.getAbsolutePath() +"/local.properties")
properties.load(propertyFile.new DataInputStream())
//gradle 就是 gradle 对象。它默认是 Settings 和 Project 的成员变量。可直接获取
//ext 前缀,表明操作的是外置属性。api 是一个新的属性名。前面说过,只在
// 第一次定义或者设置它的时候需要 ext 前缀
gradle.ext.api =properties.getProperty('sdk.api')
println gradle.api // 再次存取 api 的时候,就不需要 ext 前缀了
......
}
Project 和 自定义.gradle 对应的 Script 的关系是
- 当一个 Project apply 一个 gradle 文件的时候,这个 gradle 文件会转换成一个 Script 对象。
- Script 中有一个 delegate 对象,这个 delegate 默认是加载(即调用 apply)它的 Project 对象。但是,在 apply 函数中,有一个 from 参数,还有一个 to 参数(参考图 31)。通过 to 参数,你可以把 delegate 对象指定为别的东西。
- 在 Script 中操作一些不是 Script 自己定义的变量,或者函数时候,gradle 会到 Script 的 delegate 对象去找,看看有没有定义这些变量或函数。
所以,对于加载 自定义.gradle 的project。 有几个project,就会加载到几个project中去。其中缺省的扩展属性是添加到相应的project中了,这样可以直接拿到里面的方法, 如
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
copyOutput(true) //copyOutput 是 utils.gradle 输出的 closure
}
}
settings.gradle
include ':app'
用于指示 Gradle 在构建应用时应将哪些模块包括在内。
settings.gradle 除了可以 include 外,还可以设置一些函数。这些函数会在 gradle 构建整个工程任务的时候执行,可以在 settings 做一些初始化的工作。如:
// 定义一个名为 initMinshengGradleEnvironment 的函数。该函数内部完成一些初始化操作
// 比如创建特定的目录,设置特定的参数等
def initMinshengGradleEnvironment(){
println"initialize Minsheng Gradle Environment ....."
......// 干一些 special 的私活....
println"initialize Minsheng Gradle Environment completes..."
}
//settings.gradle 加载的时候,会执行 initMinshengGradleEnvironment
initMinshengGradleEnvironment()
//include 也是一个函数:
include 'CPosSystemSdk' , 'CPosDeviceSdk' ,
'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl'
build.gralde
实例:
// 下面这个 subprojects{}就是一个 Script Block
subprojects {
println"Configure for $project.name" // 遍历子 Project,project 变量对应每个子 Project
buildscript { // 这也是一个 SB
repositories {//repositories 是一个 SB
///jcenter 是一个函数,表示编译过程中依赖的库,所需的插件可以在 jcenter 仓库中
// 下载。
jcenter()
}
dependencies { //SB
//dependencies 表示我们编译的时候,依赖 android 开发的 gradle 插件。插件对应的
//class path 是 com.android.tools.build。版本是 1.2.3
classpath'com.android.tools.build:gradle:1.2.3'
}
// 为每个子 Project 加载 utils.gradle 。当然,这句话可以放到 buildscript 花括号之后
applyfrom: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
}//buildscript 结束
}
起始 subprojects 是一个函数,然后其参数是一个 Closure。
下面来解释以下各个Script Block :
- subprojects:它会遍历 posdevice 中的每个子 Project。在它的 Closure 中,默认参数是子 Project 对应的 Project 对象。由于其他 SB 都在 subprojects 花括号中,所以相当于对每个 Project 都配置了一些信息。
- buildscript:它的 closure 是在一个类型为 ScriptHandler 的对象上执行的。主意用来所依赖的 classpath 等信息。在 buildscript SB 中,你可以调用 ScriptHandler 提供的 repositories(Closure )、dependencies(Closure) 函数。
(Library)build.gradle
Android Library 编译出来的应该是一个 AAR 文件。但是由于有的项目有些特殊,需要发布 jar 包给其他人使用。jar 包在编译过程中会生成,但是它不属于 Android Library 的标准输出。在这种情况下,就需要在编译完成后,主动 copy jar 包到目标目录中。
//Library 工程必须加载此插件。注意,加载了 Android 插件就不要加载 Java 插件了。因为 Android
// 插件本身就是拓展了 Java 插件
apply plugin: 'com.android.library'
//android 的编译,增加了一种新类型的 ScriptBlock-->android
android {
// 你看,我在 local.properties 中设置的 API 版本号,就可以一次设置,多个 Project 使用了
// 借助我特意设计的 gradle.ext.api 属性
compileSdkVersion =gradle.api // 这两个红色的参数必须设置
buildToolsVersion = "22.0.1"
sourceSets{ // 配置源码路径。这个 sourceSets 是 Java 插件引入的
main{ //main:Android 也用了
manifest.srcFile 'AndroidManifest.xml' // 这是一个函数,设置 manifest.srcFile
aidl.srcDirs=['src'] // 设置 aidl 文件的目录
java.srcDirs=['src'] // 设置 java 文件的目录
}
}
dependencies { // 配置依赖关系
//compile 表示编译和运行时候需要的 jar 包,fileTree 是一个函数,
//dir:'libs',表示搜索目录的名称是 libs。include:['*.jar'],表示搜索目录下满足 *.jar 名字的 jar
// 包都作为依赖 jar 文件
compile fileTree(dir: 'libs', include: ['*.jar'])
}
} //android SB 配置完了
//clean 是一个 Task 的名字,这个 Task 好像是 Java 插件(这里是 Android 插件)引入的。
//dependsOn 是一个函数,下面这句话的意思是 clean 任务依赖 cposCleanTask 任务。所以
// 当你 gradle clean 以执行 clean Task 的时候,cposCleanTask 也会执行
clean.dependsOn 'cposCleanTask'
// 创建一个 Task,
task cposCleanTask() <<{
cleanOutput(true) //cleanOutput 是 utils.gradle 中通过 extra 属性设置的 Closure
}
// 前面说了,我要把 jar 包拷贝到指定的目录。对于 Android 编译,我一般指定 gradle assemble
// 它默认编译 debug 和 release 两种输出。所以,下面这个段代码表示:
//tasks 代表一个 Projects 中的所有 Task,是一个容器。getByName 表示找到指定名称的任务。
// 我这里要找的 assemble 任务,然后我通过 doLast 添加了一个 Action。这个 Action 就是 copy
// 产出物到我设置的目标目录中去
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
copyOutput(true)
}
}
/*
因为我的项目只提供最终的 release 编译出来的 Jar 包给其他人,所以不需要编译 debug 版的东西
当 Project 创建完所有任务的有向图后,我通过 afterEvaluate 函数设置一个回调 Closure。在这个回调
Closure 里,我 disable 了所有 Debug 的 Task
*/
project.afterEvaluate{
disableDebugBuild()
}
小Tip:
task myTask << {
println ' I am myTask'
}
如果代码没有加 <<,则这个任务在脚本initialization(也就是你无论执行什么任务,这个任务都会被执行,I am myTask都会被输出)的时候执行,如果加了<<,则在 gradle myTask 后才执行。