Gradle 学习记录

2020-07-11  本文已影响0人  一个追寻者的故事

学习 gradle dsl 最好莫过于官方文档了: https://docs.gradle.org/current/dsl/index.html 。 学习时可根据 Android 项目进行对应理解。如果之前对 groovy 有了解,特别是闭包,将有助于理解下面的学习。

一、基本概念

在学习 Gradle 脚本之前,有一些基本的概念,了解了这些可以帮助我们更好的编写 Gradle 脚本。

  1. Gradle脚本是 配置脚本 ,当脚本执行的时候,会对特定类型对象进行 配置。例如,作为一个 build script 执行时,实际上是对一个 Project 类型的对象进行配置。这个被配置的对象 被称为脚本的 delegate object (可按照groovy中closure的delegate理解)。可以在脚本里使用 delegate 对象的任何 属性方法
脚本类型 Delegate类型
Build script Project
Settings script Settings
  1. 每一个 Gradle 脚本都 implements Script interface(org.gradle.api.Script)。这个 接口 定义了一些列 属性方法,这些都可以被用在脚本里。正如上边看到的,每一个 Script 对象都有一个关联的 delegate 对象,当在 Script 对象中进行 属性引用 和 方法调用时,如果找不到引用 或者 方法未找到,都会接着去 delegate 对象中寻找。

3、一个 build script 由 语句脚本块 组成。语句 可以包含方法调用、属性赋值、局部变量定义。 脚本块 是一个以闭包为参数的方法调用。当闭包执行时,闭包可以看成一个配置块,对闭包的 delegate 对象进行配置。如下是一些顶级的 脚本块:

Block Description
allprojects { } Configures this project and each of its sub-projects.
artifacts Configures the published artifacts for this project.
buildscript { } Configures the build script classpath for this project.
dependencies { } Configures the dependencies for this project.
repositories { } Configures the repositories for this project.
sourceSets { } Configures the source sets of this project.
subprojects { } Configures the sub-projects of this project.
.... ....

一个 build script 也是一个 Groovy script,所以可以在脚本中使用 Groovy script 允许的元素,比如:方法定义class 定义

二、Gradle 基础使用

1、在gradle构建过程中,有三个阶段:初始化阶段配置阶段执行阶段

2、settings 文件在 初始化阶段 被执行,并且定义了哪些模块应该包含在构建内。 Gradle 会为每个settings文件创建一个 Settings对象,并调用这个对象相关的方法进行配置。

3、gradle 构建生命周期回调:配置阶段执行完毕会回调 afterEvalute; 执行阶段完成会回调 gradle#buildFinished

4、给 Project 定义扩展属性:
4.1 ext代码块

ext{
    compileSdkVersion = 29
    buildToolsVersion = "29.0.3"
}

4.2. gradle.properties 文件 (得到的都是String类型,使用时需进行类型转换)

isLoadLibA=true

5、implementation(之前的compile) 传递依赖为false;api 传递依赖为true;compileOnly(之前的provided)占位编译,只参与编译,不打入包,使用场景:
1、类库只处理编译阶段的事情,之后就不会起作用了;
2、工程中已经引入了,无须重新引入了(其实就算引入了也没关系,最后包里只有一份)

三、Project 核心类型

Project 是开发人员 和 Gradle 进行交互的主要 API。通过 Project 可以让我们用 类似编程的思想去使用 Gradle 中各种各样的特性。

3.1 project 常用相关api

Project getRootProject // 获取项目
File getRootDir // 获取项目的根路径
File getBuildDir // 获取 项目的build路径
Project getParent // 获取 项目的父项目
String getName // 获取本项目的名字
String getDescription // 获取本项目的描述
Map<String, Project> getChildProjects // 获取本项目 的子项目
Set<Project> getAllprojects // 获取本项目 和 它的子项目
Set<Project> getSubprojects // 后去本项目的子项目

3.2 提供了 file 方便的api

File file(Object path); // 通过一个相对于本项目的路径,定位一个文件。
ConfigurableFileCollection files(Object paths, Closure configureClosure); // 获取文件列表
ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure); // 根据给定的路径创建一个 FileTree 对象
File mkdir(Object path); // Creates a directory and returns a file pointing to it.
boolean delete(Object... paths); // 删除文件和文件夹
ExecResult exec(Closure closure); //执行一个外部的命令
WorkResult copy(Closure closure); //Copies the specified files.

接下来放一些例子,看看这些方法怎么使用:

/**访问项目目录下.gitignore文件,并打印出来
*/
println getContent('.gitignore')
//定义方法
def getContent(String path) {
    try {
        def file = file(path)       //可以传入一个相对当前 Project 的项目路径 的 path,  返回一个 File
        return file.text
    } catch (GradleException e) {
        println 'file not find...'
    }
    return null
}
// 进行文件 拷贝copy. 方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式
copy {
    from file('app.iml')        //从哪
    into getRootProject().projectDir.path + "/pic"  // 到哪
}
// 文件拷贝     方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式
copy {
    from file('build/outputs/apk/')        // 把生成的 apk
    into getRootProject().buildDir.path + "/apk/"  // 进行备份
    exclude {   //配置不想拷贝的进行排除

    }
    rename {    //重命名

    }
}
// 对文件树进行遍历(方便构建,只支持同一个AndroidStudio项目下的便捷方法,更复杂的文件系统处理,还需要使用 文件处理的方式)
fileTree('build/outputs/apk') { FileTree fileTree ->
    fileTree.visit { FileTreeElement element ->     //深度优先
        println 'the file name is : ' + element.file.name
        copy { CopySpec copySpec ->
            /**
             * 对闭包熟悉了之后可以去掉,让 build 脚本看起来更简洁,为什么去掉了以后照样可以访问这些闭包参数中的方法呢,
             * 因为闭包的 delegate 就是闭包参数类型的具体实现,因为delegate 的方法可直接调用,所以说白了也是调用的
             * 闭包参数的方法,只不过利用了 delegate
             */

            println delegate        //org.gradle.api.internal.file.copy.CopySpecWrapper_Decorated@XXXXXX

            copySpec.from element.file
            copySpec.into getRootProject().getBuildDir().path + '/test/'
        }
    }
}
// 创建一个task,并执行shell命令 mv
task("apkcopy") {
    doLast {
        //gradle执行阶段去执行
        def sourcePath = this.buildDir.path + '/outputs/apk'
        def desPath = '/Users/jxf/workspace/Android/valuableProject/'
        def command = "mv -f ${sourcePath} ${desPath}"

        //执行外部命令
        exec {  // Executes an external command.
            try {
                executable 'bash'
                args '-c', command
                println 'the command is execute success'
            } catch (GradleException e) {
                println 'the command is execute failed'
            }
        }

    }
}

groovy 文件操作在这里也可以使用

四、Task 核心类型

Task是Gradle执行阶段的核心。 所有的Task 按照一定的顺序组成一个 有方向的,无环的图。按照生成的有向无环图,在执行阶段执行这些Task。

4.1 定义Task的几种方式及相关
//直接通过调用 Project#task函数去创建
task helloTask {
    /**
     * 这些代码都是在 gradle 配置阶段执行的
     */
    println 'i am helloTask'
}
//通过TaskContainer去创建Task
// TaskContainer findByPath、create、register、replace 对Task进行管理
this.tasks.create('helloTask2') {
    /**
     * 这些代码都是在 gradle 配置阶段执行的
     */
    group 'jxf'
    description 'task study'
    println 'i am helloTask2'
}
// 分组可以让Task在控制面板进行归类
task helloTask3(group: 'jack', description: 'task study') { Task task ->
    /**
     * 这些代码都是在 gradle 配置阶段执行的
     */
    println resolveStrategy     //OWNER_ONLY == 2
    println owner
    println 'i am helloTask3'
}
task helloTask4(group: 'jxf', description: "task study") {
    //配置阶段执行的代码
    println('i aam helloTask4')

    /**
     * 配置 gradle 执行阶段执行的代码。让Task在Gradle执行阶段去执行的办法是通过 doFrist、doLast 方法,
给Task添加执行的处理逻辑。 Task维护了一个  List<Action<? super Task>> 的列表,当任务执行的,任务的Action会按照顺序依次执行。
     */
    doFirst {
        Task task
        println 'the task group is: ' + group
    }
    doLast {

    }
}
// 计算build执行时长的任务
def startBuildTime, endBuildTime
this.afterEvaluate { Project project ->  // gradle 配置阶段完成以后回调 (The project is passed to the closure as a parameter.)
    //找到第一个Task
    def preBuildTask = project.tasks.getByPath('preBuild')
    preBuildTask.doFirst {  //挂载到 preBuild 任务
        startBuildTime = System.currentTimeMillis()
        println 'the start tiem is : ' + startBuildTime
    }

    //找到 build Task
    def buildTask = project.tasks.getByName("build")
    buildTask.doLast {    //挂载到 build 任务
        endBuildTime = System.currentTimeMillis()
        println "the build time is: ${endBuildTime - startBuildTime}"
    }
}

默认创建的 task 都继承自 DefaultTask。我们也可以使用面向对象的程序开发方式去定义Task及相关配置。

4.2 定义Task的执行顺序:

1、通过dependsOn指定
2、指定输入、输出
3、通过Api指定

task('taskX') { Task task ->
    doLast { Task task1 ->
        println 'taskX'
    }
}

task taskY {
    dependsOn(taskX) 
 // dependsOn:taskY 依赖 taskX。当taskY单独执行时,会先执行taskX,再执行taskY; 也可单独执行taskX,不影响taskY
 // mustRunAfter:当两个任务同时执行时,需要才会有排序,否则只有一个任务的时候,不会有影响。这是跟dependsOn是有区别的。
    doLast {
        println 'taskY'
    }
}
    //动态添加Task依赖:依赖所有任务是以lib开头的Task
    dependsOn this.tasks.findAll {Task task ->
        return task.name.startsWith("lib")
    }
4.3 Task 常用API

getActions // 返回顺序task执行的Action 集合
setActions //设置task执行的action集合
getTaskDependencies //获取本Task 依赖的Task集合
getDependsOn //获取本Task 依赖的Task集合
setDependsOn //设置本Task依赖的任务集合
dependsOn(Object... paths) //给本Task添加依赖的Task
onlyIf(Closure onlyIfClosure); //本Task会被执行,仅当闭包返回true。(闭包将在任务执行阶段回调,不是在配置阶段)
doFirst 添加到Action列表的开始位置
doLast 添加到Action列表的末尾位置
boolean getEnabled(); Task是否可用
void setEnabled(boolean enabled); 设置Task是否可用。如果Task不可用,则所有action都不会被执行。但是不会影响此任务 依赖的其它任务的执行。
getInputs 获取Task的输入
getOutputs 获取Task的输出
mustRunAfter 设置此Task必须执行在另外一个Task(或者很多)后面

五、其它

5.1 gradle 版本会一直更新,gradle wrapper 用来确保构建是可重复的。

gradle wrapper 包括三部分:

5.2 一个修改输出apk文件名称的脚本
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.internal.scope.ApkData

afterEvaluate { Project project ->
    def android = project.extensions.findByType(AppExtension.class)
    android.applicationVariants.all { ApplicationVariant  variant ->
        def fileName = "app-${variant.baseName}-${variant.versionName}.apk"
        variant.packageApplicationProvider.configure { PackageAndroidArtifact -> packageApplication
            packageApplication.outputScope.apkDatas.each { ApkData apkData ->
                apkData.outputFileName = fileName
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读