Gradle五天快速入门(一)

1. gradle生命周期

gradle在编译的时候会经过不同的阶段,不同的阶段会做不同的事情

1. gradle生命周期的各个阶段

image.png

如图所示,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分类如下:

image.png

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,如下图

image.png

双击这个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的依赖方式有以下三种
image.png

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享