Gradle 学习记录

学习 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 包括三部分:

  • gradlew shell脚本
  • shell 脚本使用到的 jar 文件
  • 一个 properties 文件
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
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351