1. gradle生命周期
gradle在编译的时候会经过不同的阶段,不同的阶段会做不同的事情
1. gradle生命周期的各个阶段
如图所示,gradle的生命周期一共有三个阶段:初始化阶段,配置阶段,执行阶段
初始化阶段:执行setting.gradle的代码,解析setting.gradle中include的模块,解析各个模块中的build.gradle脚本,生成对应的project对象
配置阶段:解析各个project对象中的task,将这些task与其依赖的task构建成一个个有向无环图
执行阶段:执行task及其依赖的task
2. gradle生命周期的监听
可以在setting.gradle中使用gradle.addBuildListener监听,如下代码所示
gradle.addBuildListener(new BuildListener() {
void buildStarted(Gradle var1) {
println '开始构建'
}
void settingsEvaluated(Settings var1) {
println 'settings.gradle 中代码执行完毕'
}
void projectsLoaded(Gradle var1) {
println '初始化结束'
}
void projectsEvaluated(Gradle var1) {
println '配置阶段结束'
}
void buildFinished(BuildResult var1) {
println '构建结束 '
}
})
复制代码
还可以在setting.gradle添加如下代码
gradle.projectsLoaded {
// 初始化阶段结束
}
gradle.beforeProject { project ->
// 代表不同的模块开始配置,project是模块对应的project对象,setting.gradle中 include了多少个模块,
// 这里就会执行多少次
}
gradle.afterProject { project ->
// 代表不同的模块结束配置,project是模块对应的project对象,setting.gradle中 include了多少个模块,
// 这里就会执行多少次
}
gradle.taskGraph.whenReady {
// 代表所有projct配置结束, 也就是说配置阶段已结束
}
gradle.taskGraph.beforeTask { task ->
// 每个task执行之前都会先走这个回调
}
gradle.buildFinished {
// 代表所有的task执行结束,也就是说执行阶段已结束
}
复制代码
2. Project
在gradle生命周期中的初始化阶段,编译器会给不同module中的build.gradle文件编译出对应的project对象,project对象给我们提供了很多有用的api
1. Project核心api分类
project相关api分类如下:
1.Gradle相关api
也就是监听gradle生命周期的api
2.project相关api
让当前project可以访问并且操作根project,父project,子project, 其他Porject
3. 属性相关api
project会预先为我们准备一些属性,并且通过扩展属性(ext{})的方式让我们自定义属性
4. file相关api
让我们可以在project中更加方便的访问操作本地文件,例如复制某一个文件到指定的文件夹,
5. task相关api
project 为我们提供了创建使用task的能力,task是用来配置只在gradle生命周期执行阶段才执行的代码
6. 其他api
添加依赖,声明版本号,引入外部文件,配置代码位置,配置资源位置,配置so文件位置等api
2. project相关api
1. getAllprojects
获取工程中所有被构建的Project,在Setting.gradle中include的module都会生成一个对应的Project对象,
而getAllprojects会获取所有的Project对象,返回一个Project对象的列表,直接在根工程的build.gradle文件中添加以下代码,如下
getAllprojects().eachWithIndex{ Project project, int i ->
println("====>project.name:${project.name}")
}
复制代码
然后执行gradle clean指令,可以看到如下打印
====>project.name:GradleStudy
====>project.name:app
====>project.name:mylibrary
复制代码
说明在Setting.gradle中include的module对应的Project的名字都被打印了出来
2. getSubprojects
获取当前project下的所有子Project,如下代码所示,在根工程下的build.gradle添加以下代码
getSubprojects().eachWithIndex{ Project project, int i ->
println("====>subproject.name:${project.name}")
}
复制代码
执行gradle clean指令,打印如下
====>subproject.name:app
====>subproject.name:mylibrary
复制代码
一个子project是app,还有一个是我们新建的mylibrary module对应的project
3. getParent
获取父project对象,使用如下,在app module中的build.gradle中添加如下代码
println "====>parentProject:${getParent().name}"
复制代码
会输出如下打印,输出根工程的build.gradle文件对应的Project的名字
====>parentProject:GradleStudy
复制代码
4. getRootProject
获取根工程的project,可以在任意模块下对应的build.gradle文件中添加如下代码
println "====>rootProject:${getRootProject().name}"
复制代码
执行gradle clean指令,可以看到以下输出,成功打印出了根工程对应的project的名字
====>rootProject:GradleStudy
复制代码
5. project
可以获取指定project名字的project,并且在闭包中操作该project,先看一下源码
/**
* <p>Locates a project by path and configures it using the given closure. If the path is relative, it is
* interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p>
*
* @param path The path.
* @param configureClosure The closure to use to configure the project.
* @return The project with the given path. Never returns null.
* @throws UnknownProjectException If no project with the given path exists.
*/
Project project(String path, Closure configureClosure);
复制代码
可以看到该方法需要接收两个参数,一个是path(project名字),另一个是闭包,在这个闭包中操作该project,使用方式如下,在根工程中添加如下代码:
project("mylibrary"){Project project ->
println("====>mylibrary.project.name:${project.name}")
apply plugin: "com.android.library"
}
复制代码
执行gradle clean指令,可以看到控制台如下打印,并且没有报错信息,说明mylibrary module已经成功依赖了 “com.android.library” plugin
====>mylibrary.project.name:mylibrary
复制代码
6. allprojects
allprojects一般用于配置工程中的所有projects,与getAllprojects不同的是getAllprojects返回一个列表,allprojects没有返回值,并且接收一个闭包,在该闭包中可以操作每一个projects,如下所示,给每一个project添加仓库配置
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven {
url "https://jitpack.io"
}
maven { url "https://plugins.gradle.org/m2/" }
}
}
复制代码
7. subprojects
subprojects可以配置当前project下的任意一个project,如下代码所示:
def dir = projectDir
subprojects { project ->
project.apply from: "${dir}/config.gradle" //引入一个写好的脚本
}
复制代码
这个就给可以给任意一个子project引入config.gradle脚本
2. 属性相关api
目前Project只给我们定义了7个属性,代码如下:
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
/**
* 默认的工程构建文件名称
*/
String DEFAULT_BUILD_FILE = "build.gradle";
/**
* 区分开 project 名字与 task 名字的符号
*/
String PATH_SEPARATOR = ":";
/**
* 默认的构建目录名称
*/
String DEFAULT_BUILD_DIR_NAME = "build";
String GRADLE_PROPERTIES = "gradle.properties";
String SYSTEM_PROP_PREFIX = "systemProp";
String DEFAULT_VERSION = "unspecified";
String DEFAULT_STATUS = "release";
...
}
复制代码
幸运的是Project给我们提供了ext关键字让我们可以给Project声明扩展属性,通常都是使用ext关键字来管理全局第三方依赖及版本号,例如,可以在工程下新建config.gradle文件,然后添加代码如下
ext {
versions = [
"glide": "4.12.0",
"BRVAH": "3.0.4"
]
implementationDependencies = [
"glide": "com.github.bumptech.glide:glide:${versions.glide}",
"BRVAH": "com.github.CymChad:BaseRecyclerViewAdapterHelper:${versions.BRVAH}"
]
}
复制代码
然后在每个module下的build.gradle中使用如下代码依赖config.gradle
apply from: "../config.gradle"
复制代码
但是这样依赖不好的是当我们的工程中有多个module下的build.gradle文件都需要依赖config.gradle的时候,上面的代码就需要写多次,我们可以使用一些手段让上面的代码只些一次,如下代码所示,在根工程下的build.gradle文件中添加如下代码
def dir = projectDir
subprojects { project ->
project.apply from: "${dir}/config.gradle" //引入一个写好的脚本
}
复制代码
然后就可以在任意一个子project中使用config.gradle提供的扩展属性了,如下代码:
dependencies {
implementation implementationDependencies.glide
implementation implementationDependencies.BRVAH
}
复制代码
但是,假如工程中有多个module,并且config.gradle下面的implementationDependencies 扩展属性有多个键值对,那么以上的代码就要写多次,其实还有更加简单的写法,通过遍历map的方式来实现依赖,在不同的module下的build.gradle添加代码如下:
dependencies {
implementationDependencies.each { k, v ->
implementation v
}
}
复制代码
这样就可以让代码更加的简洁了
当然,还可以在gradle.properties 下定义扩展属性,代码如下:
// 在 gradle.properties 中
mCompileVersion = 27
// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()
复制代码
3. 文件相关 API
gradle提供的文件相关的api可以分为两种:获取文件路径api和文件操作api
1. 获取文件路径api
获取文件路径的api有三种,在任意project下的build.gradle中添加下面的代码,使用如下:
println "====>rootDir.absolutePath:${rootDir.absolutePath}"//根工程路径
println "====>projectDir.absolutePath:${projectDir.absolutePath}"//当前project路径
println "====>buildDir.absolutePath:${buildDir.absolutePath}"//build文件夹路径
复制代码
执行gradle clean指令,控制台日志打印如下:
====>rootDir.absolutePath:D:\tt-project\component\GradleStudy
====>projectDir.absolutePath:D:\tt-project\component\GradleStudy\app
====>buildDir.absolutePath:D:\tt-project\component\GradleStudy\app\build
复制代码
2. 文件操作api
1. 文件定位api
使用file(string), files(string1,string2…)可以实现文件的定位,从当前的project以及当前project下的子project中查找指定的文件,返回对应文件的File对象,在根工程下的build.gradle添加下面的代码,使用如下:
def config = file("config.gradle")
println("====>config.gradle:${config.path}")
def files = files("config.gradle","common.gradle")
files.each {File file->
println("====>${file.name}:${file.absolutePath}")
}
复制代码
执行gradle clean指令,控制台输出日志打印如下:
====>config.gradle:D:\tt-project\component\GradleStudy\config.gradle
====>config.gradle:D:\tt-project\component\GradleStudy\config.gradle
====>common.gradle:D:\tt-project\component\GradleStudy\common.gradle
复制代码
2. 文件拷贝
使用copy方法实现文件拷贝,在根工程下的build.gradle添加下面的代码,代码如下:
copy {
from new File("${rootDir.absolutePath}/app/apk") //表示从app module下的app文件夹开始复制
into new File("${rootDir.absolutePath}/apk") // 表示复制到根工程下的apk文件夹
rename {
//重命名
}
exclude {
//复制的时候排除特定文件
}
}
复制代码
这是一种DSL的写法,也就是领域特定语言,但它不是一种语言,它可以使代码变的更加灵活,且具有极强的语言表现力,从而让代码拥有极强的可读性
执行gradle clean指令后可以看到app module下的app文件夹下的文件复制到了根工程下的apk文件夹下
3. 文件树遍历
我们可以使用fileTree将指定文件夹下的文件以及子文件夹都转成文件树,然后遍历文件树的节点,从而操作不同的文件,在根工程下的build.gradle添加下面的代码,使用如下:
fileTree("${rootDir.absolutePath}/app/apk") { FileTree fileTree ->
fileTree.visit { FileTreeElement treeElement ->
if (!treeElement.file.name.endsWith("txt")) {
copy {
from treeElement.file
into new File("${rootDir}/apk")
}
}
}
}
复制代码
以上的代码就是将app module下的apk文件夹转成了文件树,然后遍历文件树的节点,判断是不是txt文件,如果是的话就复制文件到根工程下的apk目录
4. 其他api
1. 根项目下的 buildscript用于配置工程的仓库和和插件依赖,代码如下:
buildscript {
// 配置我们工程的仓库地址
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
maven { url "https://plugins.gradle.org/m2/" }
maven {
url uri('../PAGradlePlugin/repo')
}
}
// 配置Gradle的需要依赖的插件
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
...
}
复制代码
2. app moudle 下的 dependencies
不同于buildscript的classpath,classpath是用来配置gradle需要依赖的插件,而dependencies是用来配置工程需要依赖的第三方库,除了dependencies外,我们还需要了解exclude和transitive的使用,代码如下:
implementation(implementationDependencies.glide) {
// 排除依赖:一般用于解决资源、代码冲突相关的问题
exclude module: 'support-v4'
// 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
// 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B
// 中所使用的 C 中的依赖,默认都是不打开,即 false
transitive false
}
复制代码
3. SourceSet 是用来配置工程的代码位置,资源位置,so库的位置的,使用如下
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
复制代码
5. Task
1. task定义
Task的作用其实就是用来配置在gradle生命周期中的执行阶段才执行的代码,包括clean指令,其实也是一个task
2. 如何声明一个task
声明一个task的方法如下,在app module的build.gradle中添加如下代码:
task helloTask {
println "====>helloTask"
}
复制代码
执行gradle helloTask指令,控制台输出日志如下所示:
====>helloTask
复制代码
3. 如何配置一个task
首先,需要了解task有那些属性可以配置,我们看一下task的源码:
public interface Task extends Comparable<Task>, ExtensionAware {
String TASK_NAME = "name";
String TASK_DESCRIPTION = "description";
String TASK_GROUP = "group";
String TASK_TYPE = "type";
String TASK_DEPENDS_ON = "dependsOn";
String TASK_OVERWRITE = "overwrite";
String TASK_ACTION = "action";
...
}
复制代码
- name: 配置task的名字
- description: 配置task的描述信息
- group: 配置task属于哪个分组
- type:配置task的类型
- dependsOn:配置task依赖于哪个task
- overwrite:重写已经存在的task
- action:配置task在gtadle执行阶段需要执行的代码
那么,具体如何使用以上配置属性呢?看以下源码:
task helloTask(description: "helloTask", group: "study") {
println "====>helloTask"
}
复制代码
我们声明了一个名为helloTask的task,并且添加描述为helloTask,设置分组为study,打开Gradle面板,我们可以看到study分组下有一个名为helloTask的task,如下图
双击这个task,控制台会输出以下日志
====>helloTask
复制代码
4. Task执行讲解
上面例子的打印其实是在gradle生命周期的配置阶段打印的,凡是在task闭包参数中声明的代码,都会在gradle的配置阶段执行,如果我们想在执行阶段执行该如何做呢?可以使用task的doFirst{}和doLast{}函数,doFirst{}和doLast{}会在gradle的执行阶段被执行,看下面的代码:
task TestDoFirstAndDoLast{
doFirst {
println "====>doFirst1"
}
doLast {
println "====>doLast1"
}
}
TestDoFirstAndDoLast.doFirst {
println "====>doFirst2"
}
TestDoFirstAndDoLast.doLast {
println "====>doLast2"
}
复制代码
执行 gradle TestDoFirstAndDoLast指令,可以看到控制台出现下面的打印:
> Task :app:TestDoFirstAndDoLast
====>doFirst2
====>doFirst1
====>doLast1
====>doLast2
复制代码
可以看到,====>doFirst1和====>doLast1都是在 Task :app:TestDoFirstAndDoLast后面打印的,说明doFirst{}和doLast{}函数是在gradle执行阶段被执行的,那么,doFirst{}和doLast{}除了让代码在gradle执行阶段执行以外,还有什么作用呢?其实我们的工程中除了自己声明的task以外,还有系统声明的task,例如clean和build,通过doFirst{}和doLast{},我们可以对系统提供的task做监听,然后在这些task执行之前和之后插入我们自己的逻辑
5. Task执行实战
统计app module的编译时间,在app module的build.gradle中添加下面的代码,注意,仅仅只代表app module的编译时间,不代表其他module,如果需要监听其他module的编译时间,建议可以在Setting.gradle中统计,统计app module的编译时间的代码如下
// 至少统计了app module的编译时间而已,不代表其他module的编译时间
// Task 执行实战:计算 build 执行期间的耗时
def startBuildTime, endBuildTime
// 1、在 Gradle 配置阶段完成之后进行操作,
// 以此保证要执行的 task 配置完毕
this.afterEvaluate { Project project ->
// 2、找到当前 project 下第一个执行的 task,即 preBuild task
def preBuildTask = project.tasks.getByName("preBuild")
preBuildTask.doFirst {
// 3、获取第一个 task 开始执行时刻的时间戳
startBuildTime = System.currentTimeMillis()
}
// 4、找到当前 project 下最后一个执行的 task,即 build task
def buildTask = project.tasks.getByName("build")
buildTask.doLast {
// 5、获取最后一个 task 执行完成前一瞬间的时间戳
endBuildTime = System.currentTimeMillis()
// 6、输出 build 执行期间的耗时
println "${project.name} execute time is ${endBuildTime - startBuildTime}"
}
}
复制代码
执行gtadle build指令,即可看到控制台输出时间
6. Task的依赖方式和顺序
Task的依赖方式有以下三种
1. dependsOn
使用dependsOn添加task依赖,指定一个task依赖于另一个task,代码如下:
task taskX {
doLast {
println "====>taskX"
}
}
task taskY {
doLast {
println "====>taskY"
}
}
task taskZ(dependsOn: [taskX, taskY]) { // taskZ依赖于taskX和taskY
doLast {
println "====>taskZ"
}
}
复制代码
taskZ 使用了dependsOn属性,赋值为[taskX, taskY],从而完成依赖,执行taskZ,可以看到控制台输出如下日志
> Task :app:taskX
====>taskX
> Task :app:taskY
====>taskY
> Task :app:taskZ
====>taskZ
复制代码
执行taskZ前总会先执行taskX和taskY,说明taskZ已经依赖了taskX和taskY,至于taskX和taskY哪个先执行,这个就是随机的了
当然,这是在已知需要依赖那些task情况下的依赖方式,如果现在不知道具体要依赖那些task,我们还可以使用下面的方式来依赖
task taskX {
doLast {
println "====>taskX"
}
}
task taskY {
doLast {
println "====>taskY"
}
}
task H() {
dependsOn this.tasks.findAll { Task task ->// this.tasks是project提供的存放task的一个列表
return task.name.startsWith("task") && (task.name.contains("X") || task.name.contains("Y"))
}
doLast {
println "====>H"
}
}
复制代码
执行gradle H指令后,看到控制台输出如下:
> Task :app:taskX
====>taskX
> Task :app:taskY
====>taskY
> Task :app:H
====>H
复制代码
每次输出====>H前都会先输出====>taskX和====>taskY,说明H已经成功依赖了taskX和taskY
2. 通过 API 指定依赖顺序
可以通过mustRunAfter 指定task执行顺序,mustRunAfter 指定一个task需要在另一个task执行后才执行,使用如下
task A{
doLast {
println "====>A"
}
}
task B{
mustRunAfter A //指定B在A后面执行
doLast {
println "====>B"
}
}
task C{
mustRunAfter B // 指定C在B后面执行
doLast {
println "====>C"
}
}
复制代码
在控制台输入 gradle A C B, 可以看到下面的输出,说明使用mustRunAfter指定执行顺序成功了
> Task :A
====>A
> Task :B
====>B
> Task :C
====>C
复制代码
7.Task 类型
Gradle已经给我们提供了一些存在的Task,例如Delect,Coyp,我们可以指定Task的type属性来使用这些已经存在的task,使用如下:
// 1、删除根目录下的 build 文件
task clean(type: Delete) {// 指定task的type为Delete,表示使用Gradle提供的Delete Task
delete rootProject.buildDir
}
// 2、将 doc 复制到 build/target 目录下
task copyDocs(type: Copy) {// 指定task的type为Copy,表示使用Gradle提供的Copy Task
from 'src/main/doc'
into 'build/target/doc'
}
复制代码
3. QQ交流群
770892444