Gradle学习笔记

Gradle基础

Groovy语法

  • 类型定义
int vs = 1
def version = 'version1'//定义一个字符串
def s2 = "version is ${version}"
def map = ["key":"test","value":"gradle","version":5.6]
println(vs)//输出打印
println version//括号可以省略
  • 方法
task invokeMethod << {//这里的<<相当于dolast 最后执行
method(1,2)
method 1,2//括号可以省略 更简洁 在gradle中较常见
}
def method(int a,int b) {
println a+b
}
  • 闭包

闭包简单说就是代码块,如下:

def v = {
v -> println v
}
static def testMethod(Closure closure){
closure('闭包 test')
}
testMethod v

其中定义的v就为闭包,testMethod 为一个方法,传入参数Closure为闭包,然后调用闭包。

闭包可以作为参数传递,以集合的each方法为例:

list.each({println it})//传统的参数传递写法大概如此
//groovy支持当最后一个参数是闭包的时候 可以放到方法外面去
list.each(){
println it//这样看是不是感觉很像java的方法调用 看着比较简洁
}
//方法的括号可以省略 就成了gradle里常见的样式
list.each{
println it//这里的it和kotlin里let等操作符的it类似
}
  • 向闭包传递参数
task helloClosure << {
eachMap{k,v->
println "${k} is ${v}"//调用eachMap方法 并在闭包中显式声明参数
}
}
def eachMap(Closure closure){
def map = ["name":"zhf","age":"21"]
map.each{
closure(it.key,it.value)
}
}
  • 闭包委托

Groovy支持闭包方法的委托。有三种委托分别是:

1. thisObject
 2. owner
 3. delegate

其中默认情况下owner和delegate的作用相同,但delegate可以更改。thisObject和this对象也是相等的。由于优先级的不同。三种委托的执行顺序是:thisObject -> owner -> delegate

task configClosure << {
person {
name = "zhf"
age = 21
printlnPerson()//显然这里会输出咱们上面配置的信息
}
}

def person(Closure<Person> closure){
Person P = new Person()
closure.delegate = P
//委托模式优先
closure.setResolveStrategy(Closure>DELEGATE_FIRST)
closure(P)
}
class Person{
String name
int age
def printlnPserson(){
println "name is ${name},age is ${age}"
}
}

上面的了例子中我们设置了代理对象为当前创建的实例,并且设置了委托模式优先,所以我们在使用person方法创建一个person实例的时候,可以直接对该Person对象进行配置。在Gradle中还有很多类似的用法,基本上都是使用delegate来进行配置等操作

Gradle命令行

  • -i:Gradle默认不会输出很多信息,你可以使用-i选项改变日志级别为INFO
  • -s:如果运行时错误发生打印堆栈信息
  • -q:只打印错误信息 使用命令行
  • -?-h,–help:打印所有的命令行选项
  • -b,–build-file:Gradle默认执行build.gradle脚本,如果想执行其他脚本可以使用这个命令,比如gradle -b test.gradle –offline:在离线模式运行build,Gradle只检查本地缓存中的依赖
  • -D, –system-prop:Gradle作为JVM进程运行,你可以提供一个系统属性比如:- Dmyprop=myValue
  • -P,–project-prop:项目属性可以作为你构建脚本的一个变量,你可以传递一个属性值给 build脚本,比如:-Pmyprop=myValue
  • tasks:显示项目中所有可运行的任务
  • properties:打印你项目中所有的属性值

Projects 和 tasks

  • Task依赖
task dependsOnTesk {
dependsOn tesk1,tesk2
doLast {
println "dependsOn"
}
}
  • 自定义属性

Project和Task都支持自定义属性,通过ext属性来完成添加和读取

//自定义一个project属性
ext.age = 18
//通过代码块完成自定义多个属性
ext{
phone = 1234567
address = "www.test.com"
}

任务之间通过依赖来控制任务的执行时序,在创建task 的时候通过dependsOn可以指定其依赖的任务

Android Gradle

下图是一个名为ImageViewShow的Android工程结构图,包含两个module和Gradle相关文件

image-20200409150505394

gralde的项目配置是先识别 settings.gradle,然后在配置各个build.gradle。一个Android工程的Gradle包含如下内容(按上图顺序介绍):

  • 根项目的builde.gradle文件 配置项目的整体属性,比如指定使用的代码仓库、依赖的Gradle插件版本等等。
  • 子module的build.gradle文件 配置当前Module的编译参数 此处对应有app和test的两个module
  • Gradle Version的配置文件
  • 子module的规则配置文件
  • 根project的properties文件 配置Gradle的编译参数
  • 根project的Setting文件 配置Gradle的多项目管理
  • 一般用来存放该Android项目的私有属性配置,比如Android项目的SDK路径

现在来验证一下gradle文件的配置顺序,在setting和build文件里添加一句输出语句:println “···”,使用./graldew命令运行:

image-20200409152533317

显然,gradle的执行顺序按照setting -> project build.gradle -> project.app build.gradle -> project.test build.gradle 的顺序来执行的

项目build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
println "root build.gradle"
buildscript {
repositories {
google()
jcenter()

}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
//配置依赖的Gradle插件版本,Gradle插件属于第三方插件,因此这里在buildscrip块中配置谷歌的Maven库和JCenter库,这样Gradle系统才能找到对应的Gradle插件
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
google()
jcenter()

}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

模块build.gradle

println "app build.gradle"
apply plugin: 'com.android.application'//应用为gradle插件类型
//用于描述该Module构建过程中所用到的所有参数
android {
compileSdkVersion 29//配置编译该模块的SDK版本
buildToolsVersion "29.0.2"//Android构建工具的版本
//defaultConfig块用于默认配置
defaultConfig {
applicationId "com.example.imageviewshow"//App包名
minSdkVersion 21//App支持的最低SDK版本
targetSdkVersion 29//工程开发使用版本
versionCode 1//版本号 用于控制App升级
versionName "1.0"//App版本名称 就是发布的版本号
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"//默认使用的单元测试的Runner
}
//用于配置构建APK的类型 默认类型有release和debug
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//用于配置签名设置,一般用来配置release模式
signingConfigs {
release {
storeFile file('/Users/zhangfan/StudioProjects/.android/release.keystore')
storePassword 'android'
keyAlias 'androidreleasekey'
keyPassword 'android'

}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
//用于配置该module构建过程中所依赖的所有库
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha01'
implementation 'com.facebook.fresco:fresco:2.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation "io.reactivex.rxjava2:rxjava:2.2.17"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Gradle的Android插件类型

apply引入的插件id为com.android.application,说明当前模块是一个应用程序模块,Gradle的Android插件有多个类型分别为:

  1. 应用程序插件,插件id为com.android.application,会生成一个APK。
  2. 库插件,插件id为com.android.library,会生成一个AAR,提供给其他应用程序模块用。
  3. 测试插件,插件id为com.android.test,用于测试其他的模块。
  4. feature插件,插件id为com.android.feature,创建Android Instant App时需要用到的插件。
  5. Instant App插件,插件id为com.android.instantapp,是Android Instant App的入口
  • defaultConfig 常用配置补充
  1. proguardFile 代码混淆使用的配置文件
  2. proguardFiles 配置的多个ProGuard文件
  3. signingConfig 配置默认的签名信息
  • buildTypes
applicationIdSuffix 配置applicationId的后缀
debuggable 表示是否支持断点调试
jniDebuggable 表示是否可以调试NDK代码
buildConfigField 配置不同的开发环境,比如测试环境和正式环境
shrinkResources 是否自动清理未使用的资源,默认值为false
zipAlignEnabled 是否开启开启zipalign优化,提高apk运行效率
proguardFile ProGuard混淆所使用的ProGuard配置文件
proguardFiles 同事配置多个ProGuard配置文件
signingConfig 配置默认的签名信息
multiDexEnabled 是否启用自动拆分多个Dex的功能
  • 其他配置

全局配置build.gradle

如果有多个module的配置是一样的,可以将这些配置提取出来,也就是使用全局配置。全局配置有多种方式,这里介绍其中的两种

ext块定义属性

在项目build.gradle中使用ext块,如下所示:

ext{
compileSdkVersion =29
buildToolVersion ="29.0.2"
applicationId "com.example.imageviewshow"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

在某个module的build.gradle中使用配置:

apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId rootProject.ext.applicationId
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
···
}
...
}
...

这里利用的是上面介绍过的ext自定义属性来实现的,但是如果都把ext代码块写在项目根build.gradle文件里,那会十分臃肿难以维护,所以另一种方式就是将这里ext代码块写在单独的文件里,使用时引入这个文件就好了。

使用config.gradle

  1. 在项目根目录下创建config.gradle文件:
ext{
android=[
compileSdkVersion =29
buildToolVersion ="29.0.2"
applicationId "com.example.imageviewshow"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
]
dependencies =[
···
]
}
  1. 在根项目build.gradle文件里使用**apply from: “config.gradle”,这样项目所有module都能使用config.gradle的参数
  2. 最后在module里使用,使用方式与上一种方式相同

Gradle构建生命周期

主要生命周期:

  • 初始化阶段:负责判断有多少个Projects参与构建

  • 配置阶段:负责对初始化阶段创建的Projects完成配置

  • 执行阶段:根据配置阶段的配置执行任务

private void doBuildStages(Stage upTo) {
try {
//编译buildSrc 、load settings.gradle
loadSettings();
//配置project
configureBuild();
//构建task图
constructTaskGraph();
//执行task
runTasks();
//build结束
finishBuild();
} catch (Throwable t) {
}
}

初始化阶段

这个阶段中,会读取根工程中的 setting.gradle 中的 include 信息,确定有多少工程加入构建,然后,会为每一个项目(build.gradle 脚本文件)创建一个个与之对应的 Project 实例,最终形成一个项目的层次结构。 与初始化阶段相关的脚本文件是 settings.gradle,而一个 settings.gradle 脚本对应一个 Settings 对象,我们最常用来声明项目的层次结构的 include 就是 Settings 对象下的一个方法,在 Gradle 初始化的时候会构造一个 Settings 实例对象,以执行各个 Project 的初始化配置

settings.gradle

在 settings.gradle 文件中,我们可以 在 Gradle 的构建过程中添加各个生命周期节点监听:

image-20200426184555222

配置阶段

配置阶段的任务是 执行各项目下的 build.gradle 脚本,完成 Project 的配置,与此同时,会构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task。而在配置阶段执行的代码通常来说都会包括以下三个部分的内容:

  • build.gradle
  • 闭包
  • Task中的配置段语句

任何Gradle命令被执行 初始化和配置阶段的代码都会被执行

执行阶段

配置阶段结束后,Gradle 会根据各个任务 Task 的依赖关系来创建一个有向无环图,我们可以通过 Gradle 对象的 getTaskGraph 方法来得到该有向无环图 => TaskExecutionGraph,并且,当有向无环图构建完成之后,所有 Task 执行之前,我们可以通过 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 来接收相应的通知:

gradle.getTaskGraph().addTaskExecutionGraphListener(newTaskExecutionGraphListener(){    
@Override    
void graphPopulated(TaskExecutionGraph graph) {  

}
})

Hook Gradle生命周期

整个 Gradle 生命周期的流程包含如下 四个部分:

  • 解析 settings.gradle 来获取模块信息,这是初始化阶段。
  • 配置每个模块,配置的时候并不会执行 task。
  • 配置完了以后,有一个重要的回调 project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行 task 了。
  • 执行指定的 task 及其依赖的 task

Hook注意点:

Hook 点对应的监听器一定要在回调的生命周期之前添加

注册了多个 project.afterEvaluate 回调,那么执行顺序将与注册顺序保持一致

获取构建耗时

在setting.gradle中加入:

image-20200426201904356

Gradle 中,执行每一种类型的配置脚本就会创建与之对应的实例,而在 Gradle 中如 三种类型的配置脚本,如下所示:

  • Init Scrpit:对应一个 Gradle 实例,它在构建初始化时创建,整个构建执行过程中以单例形式存在
  • Build Scrpit:对应一个 Project 实例,即每个 build.gradle 都会转换成一个 Project 实例。
  • Settings Scrpit`:对应一个 Settings 实例,即每个 settings.gradle 都会转换成一个 Settings 实例。

一个 Gradle 构建流程中会由一至多个 project 实例构成,而每一个 project 实例又是由一至多个 task 构成。

Project

Project 是 Gradle 构建整个应用程序的入口,所以它非常重要。每一个 build.gradle 都有一个与之对应的 Project 实例,而在 build.gradle 中,我们通常都会配置一系列的项目依赖,比如第三方库Glide:

implementation 'com.github.bumptech.glide:glide:4.8.0'

类似于 implementation、api 这种依赖关键字,在本质上它就是一个方法调用,在上面,我们使用 implementation() 方法传入了一个 map 参数,参数里面有三对 key-value,完整写法如下所示:

implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0'

当我们使用 implementation、api 依赖对应的 aar 文件时,Gradle 会在 repository 仓库 里面找到与之对应的依赖文件,你的仓库中可能包含 jcenter、maven 等一系列仓库,而每一个仓库其实就是很多依赖文件的集合服务器, 而他们就是通过上述的 group、name、version 来进行归类存储的

API介绍

Project 中有很多的 API,根据它们的 属性和用途 我们可以将其分解为 六大部分:

![Project API](https://cdn.jsdelivr.net/gh/demoless/imgs/noteProject API.png)

1)、Project API:让当前的 Project 拥有了操作它的父 Project 以及管理它的子 Project 的能力

2)、Task 相关 API:为当前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常重要,我们将放到第四章来进行讲解

3)、Project 属性相关的 Api:Gradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我们拥有了为 Project 添加额外属性的能力

4)、File 相关 Api:Project File 相关的 API 主要用来操作我们当前 Project 下的一些文件处理

5)、Gradle 生命周期 API:即我们在第二章讲解过的生命周期 API

6)、其它 API:添加依赖、添加配置、引入外部文件等等零散 API 的聚合

核心API

每一个 Groovy 脚本都会被编译器编译成 Script 字节码,而每一个 build.gradle 脚本都会被编译器编译成 Project 字节码,所以我们在 build.gradle 中所写的一切逻辑都是在 Project 类内进行书写的

getAllprojects

build.gradle文件中获取所有project实例:

this.getProjects()

def getProjects() {
println "<==========================>"
println "Root Project Start"
println "<==========================>"
//getAllProjects 返回一个包含所有Project(根Project和子Project的Set集合)
//eachWithIndex 遍历集合、数组等可迭代的容器并返回下标 each方法仅返回project
this.getAllprojects().eachWithIndex { Project project, int index ->
//rootProject下标为0
if (index == 0) {
println "Root Project is $project"
} else {
println "child Project is $project"
}
}
}

执行./gradlew clean命令,运行结果如下:

image-20200426205023734

rootProject 与各个子工程组成了一个树形结构,但是这颗树的高度也仅仅被限定为了两层

getSubprojects

getSubProjects用于获取工程下所有子工程的实例:

this.getSubProjects()
def getSubProjects() {
println "<==========================>"
println "Sub Project Start"
println "<==========================>"
//getSubProjects方法返回一个包含子project的set集合
this.getSubprojects().each { Project project ->
println "child Project is $project"
}
}

运行结果:

image-20200426210204410

getParent

获取当前 project 的父类,需要注意的是,如果我们在根工程中使用它,获取的父类会为 null,因为根工程没有父类

在app模块下的build.gradle文件下:

this.parent()
def parent() {
println "<==========================>"
println "Parent Project is ${this.getParent().toString()}"
println "<==========================>"
}

运行结果:

image-20200426212655630

getRootProject

getRootProject 即可在任意 build.gradle 文件获取当前根工程的 project 实例

project

project 表示的是 指定工程的实例,然后在闭包中对其进行操作。在使用之前,我们有必要看看 project 方法的源码:

image-20200426214655611

project 方法中两个参数,一个是指定工程的路径,另一个是用来配置该工程的闭包,如何灵活使用project:

project("app") { Project project ->
apply plugin: 'com.android.application'
}
//更简洁写法
project("app") {
apply plugin: 'com.android.application'
}

subprojects

subprojects 可以统一配置当前project下的所有子project:

//为所有的aar文件设置配置脚本
subprojects {
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
}
}

文件API

路径获取API

  • getRootDir()
  • getProjectDir()
  • getBuildDir()
println "the root file path is: ${getRootDir().absolutePath}"
println "the build file path is: ${getBuildDir().absolutePath}"
println "the project file path is:${getProjectDir().absolutePath}"

运行效果:

image-20200427185144866

Task

def startBuildTime,endBuildTime
//在Gradle配置阶段完成之后进行操作
this.afterEvaluate { Project project ->
//通过task名字从project的tasks中获取
def preBuildTask = project.tasks.getByName("preBuild")
//doFirst获取当前时间
preBuildTask.doFirst {
//获取第一个Task执行的时间
startBuildTime = System.currentTimeMillis()
}
def buildTask = project.tasks.getByName("build")
//build最后记录当前时间
buildTask.doLast {
endBuildTime = System.currentTimeMillis()
}
println "Project build task execute time:${endBuildTime - startBuildTime}"
}

Task依赖和执行顺序

Task的执行顺序有以下三种:

be8fea3a-8d42-47b3-88c3-098e2372584b

dependsOn 强依赖

  • 静态依赖
task task1 {
doLast {
println "this is task1"
}
}
task task2 {
doLast {
println "this is task2"
}
}

//静态依赖常用方式
task task3(dependsOn:[task1,task2]) {
doLast {
println "this is task3"
}
}

//task静态依赖方式2
task3.dependsOn(task1,task2)
  • 动态依赖
//动态依赖方式
task task4 {
dependsOn this.tasks.findAll { task ->
return task.name.startsWith("task")
}
doLast {
println "this is task4"
}
}

通过 Task 指定输入输出

通过 Task 来指定输入输出,使用这种方式我们可以 高效地实现一个 自动维护版本发布文档的 gradle 脚本

Gradle原理

由gradle-wrapper到gradle的整个周期图:

gradlew-gradlmain

b975a857-0508-465c-935c-b6a70f501300

Author: zhf
Link: http://yoursite.com/2020/04/09/Gradle学习笔记/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶
简