0%

Android Gradle最佳实践系列7:Gradle Task和自定义插件开发

在前六个系列中,主要介绍了Gradle常规的使用实践和一些基础属性,这一部分将会更深入的介绍gradle的task和plugin部分,主要包括如下内容:

  1. 理解Groovy
  2. 自定义Tasks
  3. 深入Android plugin
  4. 定义自己的plugin

理解Groovy

大部分Android的开发者都是使用Java作为开发语言,groovy跟java相较而言并无多大区别,反而更易读。接下来这部分主要对groovy做一个简要的使用介绍

更多关于Groovy的使用,可以访问Goovy官网查看更多文档

关于Groovy

Goovy是继承自Java,运行在JVM上的一门脚本语言,它的宗旨是更简单,更通俗易通。接下来我们通过对比Java和Groovy语言实现上的异同来了解Groovy是如何工作的

在Java中,打印一个字符串在屏幕上如下代码实现

1
System.out.println("Hello, world!");

在Groovy中,如下

1
println 'Hello, world!'

可以看到groovy的实现有如下不同

  • 没有System.out的命名空间
  • 没有小括弧包裹方法参数
  • 没有分号在代码行末尾

上面的例子中groovy的字符串参数使用单引号引用,在groovy中单引号和双引号都可以使用,但是某些地方有些微的不同,双引号的字符可以使用占位符,如下为使用字符串的一些例子

1
2
3
def name = 'Andy'
def greeting = "hello, $name!"
def name_size = "Your name is ${name.size} characters long."

grreting值为 “hello,Andy” ,name_size的值为 “Your name is 4 characters long.”

字符的变量引用还支持动态的方法运行

1
2
def method = 'toString'
new Date()."$method"()

上面的这种写法在Java中可能觉得很奇怪,但是在像Groovy这种动态语言,这种写法很常见

Groovy中的类和成员变量

Groovy中新建类与Java类似,如下为一个简单的Groovy类包含一个成员变量和方法

1
2
3
4
5
6
class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}

可以看到上面的类,方法和成员变量都没有像 public或者private 这样的限制,不像Java中默认的访问限制,Groovy中的class和method默认是public,成员变量是private,使用 MyGroovyClass 类,如下所示:

1
2
3
def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()

通过使用关键词 def 创建变量,如上创建新的MyGroovyClass对象后,就可以通过instance访问getGreeting()方法,Groovy默认会给成员变量创建get和set方法,如

1
2
println instance.getGreeting()
println instance.greeting

上面两行代码调用其实是一样的instance.greeting实际上也是调用的instance.getGreeting()方法

方法

方法的定义groovy与java有两点不同

  • groovy不强制要求定义方法的返回类型
  • groovy总是会在方法结束加上return语句

详细可参考如下对比

Java代码如下

1
2
3
4
public int square(int num) {
return num * num;
}
square(2);

Groovy代码如下

1
2
3
4
def square(def num) {
num * num
}
square 4

通过两段代码比较,可以看到groovy代码没有int的方法返回值限制,也没有return语句(但是在实际中,为了代码的易读性,应该加上return语句),在调用方法的时候参数没有圆括号包裹。

还有一种更简洁的方法声明,可参考如下代码

1
2
3
4
def square = { num ->
num * num
}
square 8

这种方式不是标准的方法定义,而是闭包的一种实现,在Java中没有闭包的概念,但是在Groovy中闭包占据着重要的地位。

闭包

闭包是一个能够接收参数也能返回结果匿名的方法块。它能被赋值到一个变量,也能当做一个参数传递。
闭包的定义可以如上一个代码块演示,也可以更简洁一点

1
2
3
4
Closure square = {
it * it
}
square 16

在声明的时候可以使用 def 关键字,使用 Closure 可以让代码更清晰,在闭包中使用的参数,如果不声明,groovy默认使用 it 代替,但是只能在闭包只有一个参数的时候使用,如果使用it,但是调用的时候没有给闭包传入参数,那么it则为null

在Gradle中,大部分代码的实现几乎都是以闭包的方式实现的,像 android{}dependencies{} 块其实都是闭包的实现

集合

在Gradle中使用Groovy的集合主要有两种:List和Map

创建List如下所示

1
List list = [1, 2, 3, 4, 5]

遍历list集合也很简单

1
2
3
list.each(){element ->
println element
}

each 方法可以迭代list中的每一个数据,通过it参数还可以让上面的方法更精简

1
2
3
 list.each() {
println it
}

Map类型在Gradle的配置中使用的较多,定义map如下所示

1
Map pizzaPrices = [margherita:10, pepperoni:12]

获取map中的值可以通过get方法或者方括号引用

1
2
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']

map也支持一种更简洁的方法来取值,如下所示

1
pizzaPrices.pepperoni

Groovy在Gradle中的使用

通过连接groovy的一些基础概念,可以让我们更好的理解Gradle中配置代码的含义,如

1
apply plugin: 'com.android.application'

这行代码的意义,在不简写的情况下其实是这样的

1
project.apply([plugin: 'com.android.application'])

apply是project类的一个方法,参数是一个map参数,map中只用一个数据,key是“plugin”,value是“com.android.application”

还可以看到依赖的配置

1
2
3
dependencies {
compile 'com.google.code.gson:gson:2.3'
}

从上代码可知,这个代码块是一个闭包,在project的dependencies方法中,不简写的代码如下所示

1
2
3
4
5
project.dependencies({
add('compile', 'com.google.code.gson:gson:2.3', {
// Configuration statements
})
})

从上代码可知dependencies传进来的闭包交给 DependencyHandler 类的add方法中,add方法接受一个配置名称(“compile”)和依赖的路径“com.google.code.gson:gson:2.3”

更多关于Gradle配置介绍,可参考Gradle Project介绍

深入Task

自定义任务可以提高日常的开发效率,如自定义重命名apk名称的任务,处理版本号等,自定义任务可以在构建过程中的任何一步运行,非常强大

定义任务

Tasks 属于Project类,每一个task都实现 Task 接口.定义任务最简单的方式运行task方法,任务的名称作为参数

1
2
3
task hello{
println 'hello world'
}

上面代码创建的hello任务,我们运行它会得到这样的输出

1
2
3
$ ./gradlew hello
Hello, world!
:hello

初看可能会认为任务运行成功,但实际上“Hello ,world!”的输出是在任务运行之前。这个问题主要是因为gradel的task生命周期为 初始化 -> 运行配置 -> 运行任务 。task也对应三种语法:初始化语法,配置语法,任务指令语法;上面的任务其实是配置语法,即使我们运行其他的任务,“Hello,world!”也会输出

正确任务创建代码应该如下所示

1
2
3
 task hello << {
println 'Hello, world!'
}

上面的代码唯一的不同多了一个“<<”符号,这个符号表示这个任务执行的是运行任务语法而不是配置语法,为了比较两者的区别,可以参考如下代码

1
2
3
4
5
6
task hello << {
println 'Execution'
}
hello {
println 'Configuration'
}

输出结果为

1
2
3
4
$ ./gradlew hello
Configuration
:hello
Execution

因为Groovy有很多简写方式,在Gradlle中有几种定义task的方式

1
2
3
4
5
6
7
8
9
10
11
task(hello) << {
println 'Hello, world!'
}

task('hello') << {
println 'Hello, world!'
}

tasks.create(name: 'hello') << {
println 'Hello, world!'
}

上面的代码第一种和第二种方式是一样的,我们可以加单引号也可以不加,圆括弧也是可选项,上面task的定义其实就是task方法接收两个参数,一个string的任务名参数,和一个闭包, task() 是Gradle Project 类中的方法

最后一个种实现不是通过task方法,而是通过tasks(TaskContainer的实例)对象的create方法,create方法接收一个map和闭包作为参数

剖析Task

Task接口是所有任务的基础接口,包含了一些通用的属性和方法,DefaultTask实现了这个接口,我们创建的的任务都继承于DefaultTask类

准确的来讲,DefaultTask不是真正的Task接口的实现类,Gradle内部有一个AbstractTask类实现了Task接口,但是AbstractTask是内部实现,我们不能继承重写,而DefaultTask继承自AbstractTask所以我们通过继承DefaultTask来创建任务

每一个Task包含了Action对象的集合,当一个任务执行时,所有这些action按顺序执行。给Task添加action,可以通过 doFirst()doLast() 两个方法实现,这两个方法都接受一个闭包作为参数,然后传入Action对象中调用

在创建Task时,至少要实现doFirst和doLast中其中的一个,在先前我们的写法中,左位移符号(<<)其实是doFisrt方法的简写,如下为代码示例

1
2
3
4
5
6
7
8
9
task hello {
println 'Configuration'
doLast {
println 'Goodbye'
}
doFirst {
println 'Hello'
}
}

输出为:

1
2
3
4
5
$ gradlew hello
Configuration
:hello
Hello
Goodbye

可以看到doFirst总是在任务的开始执行,doLast方法在任务的结尾执行,这意味着在使用这两个方法的时候要注意顺序,尤其是在顺序很重要的逻辑上。

如果task执行需要按顺序,我们可以使用 mustRunAfter() 方法,这个方法表示两个方法的执行的先后顺序关系,一个方法必须在另一个方法执行之后才能执行

1
2
3
4
5
6
7
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.mustRunAfter task1

同时运行task1和task2会得到,不管命令中使用什么顺序,task2都在task1之后执行

1
2
3
4
5
$ ./gradlew task2 task1
:task1
task1
:task2
task2

mustRunAfter() 方法没有添加依赖关系,也就是说执行只task2,task1不会执行,如果想使任务依赖另一个任务,使用 dependsOn() 方法

1
2
3
4
5
6
7
8
9
task task1 << {
println 'task1'
}

task task2 << {
println 'task2'
}

task2.dependsOn task1

输出为:

1
2
3
4
5
$ gradlew task2
:task1
task1
:task2
task2

使用mustRunAfter方法,task1始终在task2之前执行,但是需要task1和task2都运行。使用dependsOn方法,即便只运行task2,因为task2依赖于task1,task1也会先执行后再执行task2。

使用Task简化Android打包流程

在Android中,当功能开发完毕,把apk发布到Android市场(Google Play等应用市场),需要对应用apk包进行签名,签名的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
android {
signingConfigs {
release {
storeFile file("release.keystore")
storePassword "password"
keyAlias "ReleaseKey"
keyPassword "password"
}
}

buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

配置如上其实是很不安全的,一些安全信息如密码和key都写在了代码里,如果是上传到Git中的话,很轻易别人就拿到了这些信息。这里,可以通过自定义一个task每次打包之前询问密码,或者如果觉得这样比较繁琐,可以写在一个不被版本控制的文件中,如在项目根目录创建一个 private.properties 文件,然后在 .gitignore 文件中忽略它.

private.properties 文件中内容可以这么写

1
release.password = thepassword

我们现在定义一个 getReleasePassword 的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
task getReleasePassword << {
def password = ''
if (rootProject.file('private.properties').exists()) {
Properties properties = new Properties();
properties.load( rootProject.file
('private.properties').newDataInputStream())
password = properties.getProperty('release.password')
}else{
if (!password?.trim()) {
password = new String(System.console().readPassword
("\nWhat's the secret password? "))
}
}
}

这个方法的主要作用就是判断当前项目根目录中是否有 private.properties 文件存在,如果存在就load这个文件,找到key为 releas.password 的值;为了确保没有properties文件的用户也能运行,所以当找到不到properties文件时就在控制台询问用户输入.

1
2
3
4
if (!password?.trim()) {
password = new String(System.console().readPassword
("\nWhat's the secret password? "))
}

关于上面部分的代码,首先是判断password是否为空, password?.trim() ,问号的作用是当password不为null时才调用trim方法,在groovy的if语句中字符的null或者空串都是false,所以不用单独判断

System.console().readPassword() 方法是groovy提供用来读取在控制台用户密码输入的方法,它返回的是一个字符数组,所以需要new String()去构造字符串

我们读取到密码后,就可以在gradle配置中对签名信息进行复制

这里假定keyPassword和storePassword是一致的

1
2
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password

在Gradle打包过程中,只有在发布release包的时候才会做正式签名,所以这个任务需要依赖release任务,在 build.gradle 文件中添加如下代码:

1
2
3
4
5
 tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}

上面代码的主要意图是在Android的打包流程中在最后打apk时是有一个名为 packageRelease 任务实现,这个任务就是给apk加入签名信息,在执行这个任务之前必须要获取keystore的密码,所以这个任务的执行必须要依赖于先前自定义的 getReleasePassword 任务,这里不能直接调用packageRelease.dependsOn()去设置依赖,因为Android在打包过程中具体的打包任务其实是根据build variants动态生成的,所以在gradle构建build variant之前是没有packageRelease这个任务的,只有在每次build开始时去构建build variant才会有个packageRelease任务

执行 ./gradlew assembleRelease 命令会得到如下输出

从上面的截图可知,程序打包是没有找到private.properties文件,所以加了一些友好性的提示,去如何创建private.properties文件,然后再提示用户在控制台输入密码,从而完成打包

这个task的例子简单的介绍了如何在Android build流程中完成自定义任务,接下来的部分将详细介绍Android Gradle Plugin。

深入Android Gradle Plugin

在整个Android的开发过程中,大部分我们需要自定义的task都会跟Android插件(通过 apply plugin: ‘com.android.application’ 引入android插件)关联使用

使用Android Pugin中的构建流程需要合理使用build variants,使用起来非常简单,如下所示

1
2
3
android.applicationVariants.all { variant ->
// Do something
}

applicationVariants 是所有variants的集合,通过迭代出每一个variant就可以获得对特定variant的应用,然后获得variant相应的属性,例如名称,描述等等;如果项目是一个Android Libraray那么applicationVariants应该改为librayVariants

注意到上部分代码在迭代集合内容时采用的是all()方法而不是原先介绍的each()方法,这是因为each只有在build variants创建之前触发,而all方法只要有新加入的variants就会被触发

这个技巧能够用来动态改变apk的名称,例如给apk名称加上版本号等,接下来的部分将详细介绍如何动态修改apk名称

自动重命名APK文件

在Android的打包流程中,最常见的需求就是通过给apk的名称加上版本号、渠道号重命名默认的Apk文件名称,具体实现可参考如下代码

1
2
3
4
5
6
7
8
9
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
def builtType = variant.buildType.name
def versionName = variant.versionName
def versionCode = variant.versionCode
def flavor = variant.flavorName
outputFileName = "app-${flavor}-${builtType}-${versionName}-${versionCode}.apk"
}
}

从上面的代码片段可知,每一个build variant有一个outputs集合,Android App的outputs就是一个APK文件,output对象有一个属性叫outputFileName,通过修改outputFileName就可以修改最后apk的文件名。

结合Android插件hook的功能,我们还可以创建很多自动化的任务。 接下来,我们将学习如何为应用程序的每个build variant创建一个任务。

动态创建新Task

由于Gradle的工作原理和任务构建方式便利性,我们可以基于Android构建版本,在配置阶段轻松创建自己的任务。为了演示这个强大的功能,我们将学习到如何创建一个install任务,不仅仅是安装apk,而且安装之后再运行应用程序。 install 任务是Android插件的一部分,但是如果我们在命令行界面运行 gradlew installDebug 命令来安装应用程序,在安装完成后仍然需要手动启动应用。这一节将介绍如何创建install任务并自动打开应用首页。

首先来查看之前使用的 applicationVariants 属性:

1
2
3
4
5
6
7
8
android.applicationVariants.all { variant ->
if (variant.install) {
tasks.create(name: "run${variant.name.capitalize()}",
dependsOn: variant.install) {
description "Installs the ${variant.description} and runs the main launcher activity."
}
}
}

对于每个build variant,我们需要检查它是否具有有效的install任务。因为正在创建的运行应用任务将依赖于install任务。一旦验证了安装任务存在,就会创建一个根据variant名称命名的新任务。这里需要使新任务依赖于variant的install任务,依赖设置的目的是为了在运行run任务之前先触发install任务。在tasks.create()方法中传递进来了一个闭包,闭包里面通过添加任务描述,当执行 gradlew tasks 时任务列表及其描述就会显示出来。

除了添加任务描述外,我们还需要添加实际的任务操作。在此示例中,需要启动应用程序。可以使用Android调试工具(ADB)在连接的设备或模拟器上启动应用:

1
$ adb shell am start -n com.package.name/com.package.name.Activity

Gradle有一个名为 exec() 的方法,可以执行命令行进程。为了使 exec() 工作,我们需要提供一个存在于PATH环境变量中的可执行文件,同时还需要使用 args 属性传递所有shell执行的参数,args接受一个字符串列表作为参数。 如下所示:

1
2
3
4
5
6
doFirst {
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n',"${variant.applicationId}/.MainActivity"]
}
}

要获取完整应用包名,可以使用varaint的applicationId属性,如果不同的构建变体application id后缀不一致的话,这个属性也会包含后缀,这样就会产生一个问题,如下例子所示:

1
2
3
4
5
6
7
8
9
android {
defaultConfig {
applicationId 'com.gradleforandroid'
}
buildTypes {
debug {
applicationIdSuffix '.debug'
}
}

程序包名为 com.gradleforandroid.debug ,但Activity的路径仍为 com.gradleforandroid.Activity 。为了确保Activity获得正确的加载路径,需要从applictionId中删除后缀:

1
2
3
4
5
6
7
8
9
10
11
doFirst {
def classpath = variant.applicationId
if(variant.buildType.applicationIdSuffix) {
classpath -= "${variant.buildType.applicationIdSuffix}"
}
def launchClass ="${variant.applicationId}/${classpath}.MainActivity"
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n', launchClass]
}
}

上面代码中,首先,基于应用applicationId创建了一个名为classpath的变量。然后我们找到由 buildType.applicationIdSuffix 属性提供的后缀。 在Groovy中,可以使用减号运算符从另一个字符串中减去一个字符串。这些更改可以确保在安装apk后运行应用程序不会在使用后缀时失败。

创建自定义Gradle插件

如果我们有一个Gradle任务的集合,想在多个项目中进行复用,将这些任务提取到一个自定义插件轻松解决问题。不仅我们自己可以重用构建逻辑,还能分享给其他人使用。

插件可以用Groovy编写,也可以用其他使用JVM的语言,例如Java和Scala。事实上,Gradle的Android插件的大部分是用Java和Groovy组合编写的。

创建简单插件

要提取已存储在构建配置文件中的各种构建逻辑,可以在 build.gradle 文件中创建一个插件。这是开始创建自定义插件的最简单方法。

要创建插件,需要创建一个实现插件接口的新类。这里将使用在本章前面编写的代码,动态创建运行任务。插件类定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
if (variant.install) {
project.tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
// Task definition
}
}
}
}
}

Plugin 接口定义了一个 apply() 方法。Gradle在build.gradle中使用插件时调用此方法。 project 作为参数传递,以便插件可以配置项目或使用其中的方法和属性。在前面Task例子中,就不能直接调用Android插件中相关属性了,需要通过访问 project 对象来访问相应属性。请注意,我们访问Android插件的属性,需要在我们的自定义插件应用之前将Android插件在项目中apply。否则,可能会产生异常。

task的代码与之前相同,只有一个方法调用修改,通过调用project.exec()代替调用exec()。要确保在build.gradle文件中apply插件,将此行添加到build.gradle文件中:

1
apply plugin: RunPlugin

发布插件

为了发布一个插件并共享给其他人,我们需要将插件移动到独立模块(或项目)。独立插件具有自己的构建文件用以配置依赖项和发布方式。插件模块生会成一个JAR文件,包含插件类和属性。我们可以使用此JAR文件将插件应用于多个模块和项目,并与其他人共享。

与任何Gradle项目一样,需要先创建一个build.gradle文件以配置构建:

1
2
3
4
5
6
apply plugin: 'groovy'

dependencies {
implementation gradleApi()
implementation localGroovy()
}

编写独立的Gradle插件,首先需要应用Groovy插件。Groovy插件扩展了Java插件,使得能够构建和打包Groovy类。 Groovy和纯Java都是支持的,所以如果喜欢,我们可以混合使用它们。甚至可以使用Groovy扩展一个Java类,或者反过来也行。

构建配置文件中需要包含两个依赖关系:gradleApi()和localGroovy()。添加Gradle API来从自定义插件中访问Gradle相关基础接口,localGroovy()是Gradle安装附带的Groovy SDK的发行版。 为了方便起见,Gradle默认提供这些依赖项。 如果Gradle默认没有提供这些依赖,我们需要手动下载并引用它们。

如果我们计划以公开方式分发插件,需要确保在构建配置文件中指定组和版本信息,如下所示:
    group =’com.gradleforandroid’
    version =’1.0’

    
要开始使用独立插件模块中的代码,还要确保使用正确的目录结构:

1
2
3
4
5
6
7
8
plugin
|-src
|-main
| |-groovy
| |-com.package.name
|-resources
|-META-INF
|-gradle-plugin

和其他Gradle模块一样,需要提供一个src/main目录。因为这是一个Groovy项目,main的子目录称为groovy而不是java。还有一个resource的子目录,将使用它来指定插件的属性。

在包目录中创建一个名为 RunPlugin.groovy 的文件,在其中定义插件的类:

1
2
3
4
5
6
7
8
9
10
11
package com.gradleforandroid
import org.gradle.api.Project
import org.gradle.api.Plugin

class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
// Task code
}
}
}

为了让Gradle能够找到插件,需要创建一个属性文件,将此属性文件添加到 src/main/resources/META-INF/gradle-plugins/ 目录。文件的名称需要与我们的插件的id匹配。对于RunPlugin,该文件名为com.gradleforandroid.run.properties,文件内容如下:

1
implementation-class=com.gradleforandroid.RunPlugin

属性文件包含的唯一的东西是实现了Plugin接口类的包和名称。

当插件和属性文件准备就绪后,我们可以使用 gradlew assemble 命令打包插件。最终会在输出目录中创建一个JAR文件。如果我们还想把插件推送到Maven仓库,就还需要应用Maven插件:

1
apply plugin: 'maven'

接下来,配置uploadArchives任务,如下所示:

1
2
3
4
5
6
7
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('repository_url'))
}
}
}

uploadArchives任务是预定义的任务。在任务中配置存储仓库后,就可以执行此任务来发布插件。这里就不详细介绍如何设置Maven存储库。

如果我们想让我们的插件公开,可以考虑发布到Gradleware的插件仓库。插件仓库有很多Gradle插件集合(不只是特定于Android开发)。 我们可以在的官方文档中找到有关如何发布插件的详细信息。

本文档不包括对自定义插件编写测试代码,但如果计划使插件公开可用,强烈建议进行代码的测试。我们可以在Gradle用户指南中找到有关编写插件测试的更多信息。

使用自定义插件

要使用插件,需要将插件添加为buildscript块的依赖项。 首先,配置一个新的依赖仓库。依赖仓库的配置取决于插件的分发方式。其次,就是需要在依赖关系块中配置插件的类路径。

这里如果包括我们在前面的例子中创本地生成的JAR文件,可以定义一个 flatDir 存储库:

1
2
3
4
5
6
7
8
buildscript {
repositories {
flatDir { dirs 'build_libs' }
}
dependencies {
classpath 'com.gradleforandroid:plugin'
}
}

如果将插件上传到Maven或Ivy仓库,配置会有不懂。 在第3章“管理依赖关系”中介绍了依赖关系管理,因此在这里就不再重复。

在设置依赖之后,就需要应用插件:

1
apply plugin: com.gradleforandroid.RunPlugin

使用apply()方法时,Gradle创建一个插件类的实例,并执行插件自己的apply()方法,我们就可以正常使用自定义插件了。

总结

在本章中,我们学习到了Groovy与Java的不同,以及如何在Gradle中使用Groovy,还看到了如何创建自定义的任务,以及如何hook任务到Android插件中。

在本章的最后一部分,还研究了如何创建插件,并确保可以通过创建一个独立的插件在多个项目中复用它们。 其实还有很多深入的知识不是本文档全部能覆盖的,更多的知识可以参考Gradle用户指南