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 属性相关的 ApiGradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我们拥有了为 Project 添加额外属性的能力

4)、File 相关 ApiProject 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
  • 微信
  • 支付寶