十分钟搞定 Gradle

栏目: Groovy · 发布时间: 4年前

内容简介:学习过程中,什么阶段最痛苦?大概是某个知识点的碎片信息学习了很多却仍然无法窥其门径,也就是似懂非懂的时候。对于 Gradle,笔者之前就是这种状态。在亲手完成了一个需求后,发现 Gradle 也不过如此。由于笔者做需求时采用的是倒扒皮的方式,即先 google 搜索如何解决问题,再阅读官方 User Guide,最后总结反思,所以用了半天的时间,还踩了一些坑。如果按照本文介绍,按部就班地学习,大概十分钟就够了。所谓一通则百通,窥其门径后,若有其它需求,直接查阅 API 即可。笔者是做安卓整机开发的,目前接手

学习过程中,什么阶段最痛苦?大概是某个知识点的碎片信息学习了很多却仍然无法窥其门径,也就是似懂非懂的时候。对于 Gradle,笔者之前就是这种状态。在亲手完成了一个需求后,发现 Gradle 也不过如此。

由于笔者做需求时采用的是倒扒皮的方式,即先 google 搜索如何解决问题,再阅读官方 User Guide,最后总结反思,所以用了半天的时间,还踩了一些坑。如果按照本文介绍,按部就班地学习,大概十分钟就够了。所谓一通则百通,窥其门径后,若有其它需求,直接查阅 API 即可。

案例

笔者是做安卓整机开发的,目前接手了一个新项目,其 APP 分为两个版本,一个是系统预置(private),一个供其它品牌手机安装使用(public)。其中 public apk 需要打包到 private apk 的 assets 目录下,以在 private apk 上实现扫码安装 public apk 的功能。两个版本的代码目前是手动维护,很不方便。笔者便想通过创建自定义的 Task,让 Gradle 来自动构建。

问题

  • 如何创建 private、public 两个 build variants(构建变体)?
  • 如何配置 public 版本在 private 版本之前构建(因为 private 版本依赖 public 版本生成的 apk)?
  • public 版本构建完成后,如何自动复制其生成的 apk 到 private 版本的 assets 目录下?

解决方案

  • 关于构建变体,其实就是一次编译,输出多个版本的 apk,具体内容请参考官方文档中文版《配置构建变体》
  • 两个构建变体,说明对应两个 assemble task,那么只要获获取到这两个 task 对象,然后设置其依赖关系即可
  • Gradle 文件支持 groovy 编写,groovy 又是基于 java 的,所以即使不熟悉 groovy 的语法,也可以用 java 写出来。不过对于复制这种操作,Gradle 有现成的 API

如何编写

方案很清晰,但是怎么写呢?似乎无从下手。比如如何获取两个构建变体对应的 assemble task?如何创建一个 copy task?如何在执行 copy task 之前先执行 delete task(删除 assets 目录下的旧 apk) 以及 sign task(签名 public apk)?

笔者一顿 google 搜索之后解决了这些问题,不过也踩了一个坑,就是自定义 task 内的代码为何总是被执行:

File -> Demo/app/build.gradle

android {
    ...
}

task deleteOldPublicApk(type: Delete) {
    println("-----------> delete the old pubic apk begin")
    delete 'src/privateApp/assets/Public.apk'
    println("-----------> delete the old pubic apk end")
}

dependencies {
    ...
}
复制代码

如上所示的 deleteOldPublicApk task,只要在 terminal 中 输入 gradlew assemble 必然会首先打印:

-----------> delete the old pubic apk begin
-----------> delete the old pubic apk end
复制代码

相信很多不熟悉 Gradle 的人都会犯这样的错误,stackoverflow 上有人也发出了同样的疑问 Why is my Gradle task always running?

后来笔者阅读了 Gradle 的官方文档《Build Lifecycle》,恍然大悟,应该这么写:

task deleteOldPublicApk(type: Delete) {
    doFirst {
        println("-----------> delete the old pubic apk begin")
    }
    delete 'src/privateApp/assets/Public.apk'
    doLast {
        println("-----------> delete the old pubic apk old")
    }
}
复制代码

痛定思痛,笔者决定将 Gradle 的入门在此做一个总结。

入门

Gradle 的入门其实很简单,不需要深入学习 Groovy(随用随查),也不用记 Gradle 的 API(随用随查)。只需要了解几个核心概念(构建模型、构建的生命周期、Project、Task、TaskContainer),就能做到一通百通了。

构建模型的核心

十分钟搞定 Gradle

左边是构建模型的抽象,右边是一个 java 工程的具体实现。Gradle 的核心就是左边的抽象模型(有向无环图),也就是说一个完整的构建过程,其实就是一系列 Task 的有序执行。

构建生命周期

三个构建阶段

  1. Initialization:配置构建环境以及有哪些 Project 会参与构建(解析 settings.build)
  2. Configuration:生成参与构建的 Task 的有向无环图以及执行属于配置阶段的代码(解析 build.gradle)
  3. Execution:按序执行所有 Task

示例

File-> settings.gradle

println 'This is executed during the initialization phase.' // settings.gradle 中的代码在初始化阶段执行
复制代码

File->Demo/app/build.gradle

println 'This is executed during the configuration phase.' // 在配置阶段执行

// 普通的自定义 Task
task testBoth {
	doFirst {
	  println 'This is executed first during the execution phase.' // doFirst 中的代码在执行阶段执行
	}
	doLast {
	  println 'This is executed last during the execution phase.' // doLast 中的代码在执行阶段执行
	}
	println 'This is executed during the configuration phase as well.' // 非 doFirst 或者 doLast 中的代码,在配置阶段执行
}

// 继承自 Copy 的 TasK
task copyPublicApk(type: Copy) {
    doFirst {
        println("-----------> copy the new pubic apk begin")
    }
    // from, into, rename 都继承自 Copy,所以即使直接写也是在执行阶段执行
    from 'build/outputs/apk/app-publicApp-release.apk'
    into file('src/privateApp/assets')
    rename { String fileName ->
        fileName = "Public.apk"
    }
    doLast {
        println("-----------> copy the new pubic apk end")
    }
}
复制代码

Project

一个 build.gradle 对应一个 Project 对象,在 gradle 文件中可通过 project 属性访问该对象。而 rootProject 属性代表的是根 Project 对象,即项目根目录下的 build.gradle 文件。

Project 由一系列的 task 组成,你可以自定义 task,也可以继承已有的 task:

十分钟搞定 Gradle

Project 还有自己的属性和方法:

十分钟搞定 Gradle

Task types 以及 Project 的属性和方法都可以在Groovy DSL Reference 中查到。

Task

在 gradle 文件中,我们一般使用 task 关键字来定义一个 task,通过 task 的名字就可以直接访问该 task 对象:

File -> Demo/app/build.gradle

task customTask() {
    doLast {
        println 'hello, this is a custom task'    
    }
}
复制代码

如何查找一个 task 呢?通过 TaskContainer 对象,在 gradle 文件中通过 tasks 属性来访问该对象:

File -> Demo/app/build.gradle

afterEvaluate {
    def aTask = tasks.getByName('assembleDebug')
    println "aTask name is ${aTask.name}"
    aTask.dependsOn(customTask)
}
复制代码

如上所示,我们获取到了 assembleDebug 这个 Task 的实例,并设置它依赖之前定义的 customTask,所以执行 assembleDebug 时就会先执行 customTask。

TaskContainer 还有很多查找 task 的方法,具体可以查询Task Container。

Gradle API 查阅指导

了解了构建模型及三大阶段,接下来就是如何查阅 API 手册了。因为 Android Studio 对 Gradle 文件的编写支持很不友好,笔者经常会出现代码没有智能提示、无法自动补全、无法代码跳转等问题,而且语法高亮也是弱的可怜。所以,必须掌握手动查阅 Gradle API 的方法。

不过现在 Gradle 文件也可以使用 kotlin 编写,语法清晰,可读性好,而且支持语法高亮、代码补全、代码跳转等。感兴趣的可以参考官方迁移教程 《Migrating build logic from Groovy to Kotlin》

离线查看

Gradle 网站现在也可以正常访问了,不过 Android Studio 在下载 Gradle 插件时,已经自动将用户指南、DSL参考、API参考下载到本地了:

十分钟搞定 Gradle
  • dsl:里面的内容跟 javadoc 差不多,不过是经过分类的,交互体检比 API 文档要好,主要关注核心类型里的 Project、Task 和 TaskType,具体关注里面的属性和方法,以及继承的属性和方法,用到什么就去查什么
  • javadoc:java api 文档,可以查看类的继承以及实现情况,快速索引
  • userguide:用户指南,比如 build lifecycle 的介绍,不过 html 内部的链接点击无法跳转,还好目录下有个带书签的 pdf 版

在线文档

离线文档不一定是最新的,有需要时可以查看在线文档

示例

下面这段配置大家应该都见过,我们现在想搞清楚里面的 main 是什么意思:

sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            ...
        }
    }
复制代码

直接到离线的 javadoc 中查找 SourceSet:

十分钟搞定 Gradle

显然 main 是一个 SourceSet 对象,名字为:

十分钟搞定 Gradle

而 sourceSets 则是一个 SourceContainer 对象,组织并管理一系列的 SourceSet 对象。

Groovy API 查阅指导

对于 Android 开发者来说,学习 Groovy 主要是为了阅读别人写的 build.gradle 文件是什么意思,因为 Groovy 是基于 java 的,所以其实完全可以使用 java 语法,只是不够简洁而已。

笔者认为 Groovy 语法最蛋疼的地方就是函数调用的圆括号可以省略,而属性赋值的 = 也可以省略,这很容易导致属性赋值与函数调用傻傻分不清楚,比如:

def aMethod(String x, String y) {
    println(x + y)
}

android {
    aMethod 'groovy', '函数调用的圆括号可以省略'
    ...
    println "project desp is: $description"
    // description 是 Project 对象的属性之一,此处将其重新赋值,且省略了 '='
    description 'The Gradle Groovy DSL allows to omit the = assignment operator when assigning properties'
    println "project desp is: $description"
}

dependencies {
    ...
}
复制代码

在 terminal 中输入 gradlew assemble 将会输出

groovy函数调用的圆括号可以省略
project desp is: null
project desp is: The Gradle Groovy DSL allows to omit the = assignment operator when assigning properties
复制代码

你看这个 aMethod 调用,像不像属性赋值?你看这个属性赋值,像不像函数调用?

十分钟搞定 Gradle

以下来自官方迁移至 Kotlin 编写 Gradle 文件的吐槽:

As a first migration step, it is recommended to prepare your Groovy build scripts by

  • unifying quotes using double quotes,
  • disambiguating function invocations and property assignments (using respectively parentheses and assignment operator).

The latter is a bit more involved as it may not be trivial to distinguish function invocations and property assignments in a Groovy script. A good strategy is to make all ambiguous statements property assignments first and then fix the build by turning the failing ones to function invocations.

建议按照以下章节顺序,快速学习并入门 Groovy:

  • Variable definition:了解变量是怎么定义的,记住 def 这个关键字,可以用来定义变量、方法和闭包
  • Optionality:了解函数调用的圆括号是怎么省略的
  • Strings: 字符串的定义方式,以及如何在字符串中引用字符串变量(String interpolation)
  • Method definitionNamed parameters 、Default arguments:了解怎么定义方法、方法的具名参数、方法参数的默认值
  • Fields and properties :了解字段与属性的区别
  • Closures:什么是闭包,以及如何定义闭包(其实就是匿名函数)

结语

所谓难而不会,会而不难,希望看完本文,各位能有一种 Gradle 也不过如此的感觉。

十分钟搞定 Gradle

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

总开关

总开关

[美] 吴修铭 / 顾佳 / 中信出版社 / 2011-8 / 49.00元

当收音机经历从真空管收音机到半导体收音机,再到电晶体收音机的发展升级时,人们觉得自己的资讯来源美满得无可复加了。当约翰.洛吉.贝尔德发明了电视以后,在很长一段时间内,人们都认为电视就是他们所拥有的﹑也是所愿意拥有的最好的资讯媒介。 时至今日,互联网的震撼不亚于以往任何媒介,它给我们带来了最大的信息量,最便捷的自我表达,最迅速的沟通。互联网似乎比以往任何媒介都具有优越性。在互联网成为这个时代主......一起来看看 《总开关》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具