Gradle For Android(7)--创建Task以及Plugin

2018-10-24 15:12:42 浏览数 (1)

介绍

到目前为止,我们已经看到了很多Gradle构建的属性,并且知道了怎么去执行Tasks。这一章,会更多的了解这些属性,并且创建我们自己的Task。一旦知道如何自定义Task之后,就可以完成更多的事情,并且自定义自己的插件,而在多工程中使用这些Task和Plugin。

之前我们看到了如何创建自定义Task,并且了解了一些Groovy脚本。知道Groovy也帮我们理解Gradle如何工作,并且为什么构建配置文件可以这样配置。

这一章会从下面的角度来介绍:

  • Understanding Groovy
  • Getting started with tasks
  • Hooking into the Android plugin
  • Creating your own plugins

Understanding Groovy

Groovy对于Java开发者而言非常容易阅读,但是如果没有一个简单的介绍的话,Groovy代码也是一个比较难的任务。

Groovy基于Java并且在JVM中执行。它的宗旨是变得更简单,更直接的语言,就像脚本语言一样。而我们将Grovvy和Java对比,可以让我们更好的了解Groovy如何工作的,并且更清楚的了解到这两种语言的区别。

在Java中打印一个字符串如下:

代码语言:javascript复制
System.out.println("Hello, world!");

而在Groovy中如下:

代码语言:javascript复制
println 'Hello, world!'

我们可以立即发现一些关键的区别:

  • 没有System.out的命名空间
  • 没有参数路径
  • 结尾没有分号

示例中使用单引号包围着一个String。你也可以使用单引号或者双引号,但是他们是有区别的。双引号的String可以包含一些差值表达式。差值表达式可以值或者函数来代替其中的占位符。而占位符表达式会包含多个值,并且通过$前缀来代表值。例如:

代码语言:javascript复制
   def name = 'Andy'
   def greeting = "Hello, $name!"
   def name_size "Your name is ${name.size()} characters long."

greeting值代表着Hello,Andy字符串。并且name_size的值为Your name is 4 characters long

字符串差值器允许我们执行动态代码,比如说下面的代码是打印正确的日期:

代码语言:javascript复制
def method = 'toString'
new Date()."$method"()

这在Java中看起来很奇怪,但是在动态语言的里面确实很平常的。

Classes and members

在Groovy中创建Class如下,包含一个成员和一个函数:

代码语言:javascript复制
class MyGroovyClass {
       String greeting
       String getGreeting() {
           return 'Hello!'
       } 
}

注意到成员和函数都没有类似于private ,public的访问权限。而默认的访问权限和Java不同,Groovy中的类都是Public的,就和Method一样,但是成员变量却是私有的。 如果要创建一个MyGroovyClass变量,如下:

代码语言:javascript复制
   def instance = new MyGroovyClass()
   instance.setGreeting 'Hello, Groovy!'
   instance.getGreeting()

我们可以使用def关键字来创建一个新的变量。一旦创建出了一个变量,就可以操作它的成员了。Groovy自动添加了访问权限,你也可以重写他们。就像我们定义了getGreetingMyGroovyClass中。如果没有指定的话,可以使用setter和getter方法来访问成员变量。 如果你尝试直接调用一个成员,那么需要调用getter方法即可。也就是说,你不需要定义instance.getGreeting()函数,你可以直接调用instance.geeting即可。

代码语言:javascript复制
println instance.getGreeting()
println instance.greeting

上面两行代码完成了相同的事情。

Methods

就像变量一样,我们不需要指定具体的返回类型给Method。虽然为了比较清晰的能够看清楚函数的结构,我们也会定义好返回值。另外一个不同的地方就是,Groovy默认会有返回值,而不需要使用return关键字。

例如Java中返回一个值的平方:

代码语言:javascript复制
public int square(int num) {
       return num * num;
} 
square(2);

你需要指定函数为public,并且返回的类型,参数,以及返回对应类型的值。同样的函数定义在Groovy中如下:

代码语言:javascript复制
def square(def num) {
       num * num
}
square 4

没有返回类型,没有参数类型。通过使用def关键字来代替一个具体的类型,并且返回具体的值也没有通过return返回。当调用这个函数的时候,也不需要括号和分号。另外一种Groovy的定义方式如下:

代码语言:javascript复制
def square = { num ->
       num * num
}
square 8

这不是一个常规的方法,而是一个闭包。闭包的概念和Java中不一样,但是在Groovy和Gradle中尤为重要。

Closures

闭包是匿名的代码块,能够接受参数并且返回一个值。它能够被分配给变量,也能够作为参数传递给函数。

你可以定义一个简单的闭包,在花括号中添加代码块即可。如果你希望它能够更直接一些,那么可以在定义中添加类型,例如:

代码语言:javascript复制
Closure square = {
       it * it
}
square 16

通过添加Closure定义让每个人都知道这段代码是闭包。如果你不想在闭包中指定参数具体的类型,Groovy会自动添加一个。这个参数的名字就叫做it。如果调用者没有指定任何参数,那么这个参数就会是null。这可以使代码更加简洁,但仅当闭包只用一个参数时才有用。

在Gradle的上下文中,我们总是使用闭包。例如,android代码块以及dependencies代码块都是闭包。

Collections

Gradle中有两个比较重要的概念,List和Map。 在Groovy中创建List很简单,不需要特殊的初始化:

代码语言:javascript复制
List list = [1, 2, 3, 4, 5]

列表的迭代器也很简单。你可以通过each方法来遍历每个元素:

代码语言:javascript复制
list.each() { element ->
       println element
}

each函数可以让你访问List中的每个元素。而我们也可以通过it变量更方便的调用:

代码语言:javascript复制
list.each() {
       println it
}

另外一种类型就是Map。Map通常用在Gradle的设置和函数中。Map中保存着K-V的列表。我们可以通过以下方式定义Map:

代码语言:javascript复制
Map pizzaPrices = [margherita:10, pepperoni:12]

如果要访问Map中的某一条,则使用get方法或者单引号访问即可:

代码语言:javascript复制
 pizzaPrices.get('pepperoni')
 pizzaPrices['pepperoni']

Groovy也为该功能提供了一个更简便的方法,你可以通过.的方式来访问某个值:

代码语言:javascript复制
pizzaPrices.pepperoni

Groovy in Gradle

打开一个Gradle的build.gradle文件,看整个构建中的Android Plugin应用的地方:

代码语言:javascript复制
apply plugin: 'com.android.application'

这段代码是Groovy精简版,如果原版的Groovy代码应该是:

代码语言:javascript复制
project.apply([plugin: 'com.android.application'])

重写Groovy的精简的方法,我们调用了Project类的``apply方法。而apply方法只有一个参数,而该参数是一个Map,里面包含了Key为plugin,Value为com.android.application```。

另外一个例子,就是dependencies代码块,之前我们定义dependencies如下:

代码语言:javascript复制
dependencies {
       compile 'com.google.code.gson:gson:2.3'
}

我们现在知道这个代码块是一个闭包,调用了Project对象的dependencies函数。这个闭包传入的是一个DependencyHandler对象,而这个对象中存在add函数。 这个函数接受了三个参数,一个String定义了配置,一个对象定义了依赖库,以及一个闭包可以指定依赖的属性。全部展开如下:

代码语言:javascript复制
project.dependencies({
       add('compile', 'com.google.code.gson:gson:2.3', {
           // Configuration statements
       })
})

如果希望了解更多的Groovy在Gradle中的内幕,最开始可以看看Project的官方文档。地址为:http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html

Getting started with tasks

自定义Gradle任务可以提升我们的开发效率。Tasks可以操作已存在的构建流程,添加新的构建步骤,并且影响构建的输出。我们可以执行一些简单的任务,比如说可以通过Hook Gradle的Android Plugin重命名一个已经生成的APK。Tasks也允许你执行更多复杂的代码,以至于我们可以在APK打包前生成多Density的图片。例如,一旦你知道如何创建自定义Tasks了,你就会发现你可以改变构件流程了。

Defining tasks

Tasks属于Project对象,并且每个Task实现了Task接口。定义一个Task最简单的方法就是使用Tasks的名字作为参数执行Task方法即可。例如:

代码语言:javascript复制
task hello

这将创建出Task。但是不会做任何事情,如果我们希望添加一些事件,则可以通过以下方式:

代码语言:javascript复制
task hello {
     println 'Hello, world!'
}

当执行这个任务的时候,就会发现:

代码语言:javascript复制
$ gradlew hello
Hello, world!
:hello

从这个输出,可以看出:Hello,world!在任务执行前被打印出来了。回顾一下之前说的Gradle构建流程,有三个阶段:初始化阶段,配置阶段,执行阶段。当按照上述例子添加Task时候,实际上是配置了这个Task。甚至如果你执行其他的任务,Hello,World!这条消息仍然会出现。

如果你希望在执行阶段添加一些事件的话,则可以使用:

代码语言:javascript复制
task hello << {
     println 'Hello, world!'
}

唯一不同的是在闭包前加入了<<。这告诉了Gradle代码是在执行阶段,而不是在配置阶段。

为了证明这个区别,我们可以在build.gradle中加入:

代码语言:javascript复制
task hello << {
  println 'Execution'
}
hello {
  println 'Configuration'
}

我们定义了一个当它执行的时候会打印的Task。我们也定义了一个在Configuration阶段打印的的Task。即使它在真正的Task之后定义的,也会首先执行。输出的结果如下:

代码语言:javascript复制
$ gradlew hello
Configuration
:hello
Execution

由于Groovy有很多简洁定义的方式,以下为一些示例:

代码语言:javascript复制
task(hello) << {
     println 'Hello, world!'
}
task('hello') << {
     println 'Hello, world!'
}
tasks.create(name: 'hello') << {
     println 'Hello, world!'
}

第一个和第二个代码块通过两种不同的方式实现同一个效果。你可以使用单引号,也可以使用括号。在这两个代码块中,我们调用的是task()函数,它会有两个参数,一个是Task的名字,另外一个是一个闭包。task()函数就是Gradle中Project类中的一部分。

最后一个代码块则不是使用task()函数。它用的是一个名为tasks的对象,而这个对象则是TaskContainer的实例。并且,这个实例代表着每一个Project。它提供了create函数,而这个函数会通过一个Map对象和一个闭包作为参数,并且返回一个Task对象。

Anatomy of a task

Task接口是所有Task,以及定义一系列Properties和Methods的基础。所有的这些都被一个默认的Class实现了,它的名字叫做DefaultTask。这是标准的Task类型的实现,当创建一个新的Task的时候,它会基于DefaultTask

每个Task都包含了一系列Action对象。当Task被执行的时候,这些Action都会按照顺序执行。我们可以使用doFirstdoLast函数来添加Action。这些方法都添加一个闭包作为参数,并且把他们包装到一个Action对象中。

你只需要通过doFirst()doLast()来在Execution阶段来执行代码。而<<符号则其实代表着在doFirst中定义了Action。举例如下:

代码语言:javascript复制
task hello {
     println 'Configuration'
     doLast {
       println 'Goodbye'
      }
     doFirst {
       println 'Hello'
     } 
}

当我们执行hello这个任务时,则会打印出:

代码语言:javascript复制
$ gradlew hello
Configuration
:hello
Hello
Goodbye

即使打印Goodbye那行代码定义在Hello之前,它也会在Task执行的时候,按照正确的位置打印出来。你也可以多次使用doFirst()doLast(),例如:

代码语言:javascript复制
task mindTheOrder {
     doFirst {
       println 'Not really first.'
     }
     doFirst {
       println 'First!'
     }
     doLast {
       println 'Not really last.'
    }
     doLast {
       println 'Last!'
    } 
}

执行完这个任务,就会打印出:

代码语言:javascript复制
$ gradlew mindTheOrder
:mindTheOrder
First!
Not really first.
Not really last.
Last!

注意,doFirst()函数会加在Task的Action集合最开始的地方,而doLast()添加的Action则在最后的位置。这也就意味着,我们使用这些函数的时候需要很小心,尤其注意它的顺序。

如果它依赖于某个顺序执行的任务的话,那么可以使用mustRunAfter()函数。这个函数允许你影响Gradle构建的Dependency的DAG。当你使用mustRunAfter时,需要指定两个任务,其中一个必须在另外一个之前执行:

代码语言:javascript复制
task task1 << {
    println 'task1'
}
task task2 << {
    println 'task2'
}
task2.mustRunAfter task1

执行task1和task2将会得到task1在task2之前执行,而忽略你所指定顺序。

代码语言:javascript复制
$ gradlew task2 task1
:task1
task1
:task2
task2

mustRunAfter()函数不会在两个Task之间添加依赖关系。它可以在Task1不执行的情况下,仍然可以执行Task2。如果你希望添加两个Task之间的依赖关系的话,那么需要使用dependsOn()。例如:

代码语言:javascript复制
task task1 << {
     println 'task1'
}
task task2 << {
     println 'task2'
}
task2.dependsOn task1

而当你只执行task2,而不执行task1的时候:

代码语言:javascript复制
$ gradlew task2
:task1
task1
:task2
task2

使用mustRunAfter()的时候,同时执行task2和task1,并且在task2优先执行的时候,他们还是会有执行的依赖关系。而dependsOn()的话,task2必须和task1挂钩,即使没有明确的说明。这是一个很重要的点。

Using a task to simplify the release process

在发布App之前,你需要对APK进行签名。而签名前,需要创建自己的keystore,其中包含了很多private keys。当你创建完keystore后,你可以在Gradle中定义签名的配置了。例如:

代码语言:javascript复制
android {
    signingConfigs {
        release {
            storeFile file("release.keystore")
            storePassword "password"
            keyAlias "ReleaseKey"
            keyPassword "password"
        } 
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    } 
}

这种方式的缺点就是密码会被铭文保存在仓库中。如果你正在为开源做奋斗的话,那不要用这种方式。任何一个拥有keystore文件和password的人都可以使用你的ID发布App。

为了避免这种情况,你可以创建一个Task,在每次打Release包的时候询问Release的Password。这会有一点麻烦,而且在自动持续集成构建Release包的情况下也是不可能的。一种比较好的解决方案就是,创建一个配置文件保存keystore的密码,而这个配置文件不在仓库中。

我们可以在根目录下提供一个名为private.properties的文件,并且添加:

代码语言:javascript复制
release.password = thepassword

我们假设Keystore和key的密码相同。如果你有两个不同的密码,那么则可以创建第二个属性。一旦设置完成,你可以定义一个新的Task,名为getReleasePassword

代码语言:javascript复制
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')
        } 
}

这个任务会在根目录寻找一个名为private.properties的文件。如果文件存在,那么Task会加载所有的properties。并且properties.load()函数会查找Key-Value对,就像我们在properties文件中定义的release.password一样。

为了保证没有private properties文件的人也可以运行这个脚本,或者处理如果文件存在,但是password属性不存在的情况,我们可以添加一个fallback。如果password仍然为空,那么可以在console中询问Password:

代码语言:javascript复制
if (!password?.trim()) {
    password = new String(System.console().readPassword ("nWhat's the secret password? "))
}

在Groovy中检查字符串是否为空是一个很简单的操作。用?标志的password?.trim()检查了password是否为null,并且使用trim()避免password为空。

使用new String是必须的,因为System.readPassword()会返回一个字符数组,然后通过String来转换成字符串。一旦我们拥有了keystore的密码,我们就可以在release构建中配置签名:

代码语言:javascript复制
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password

现在我们已经完成我们的任务,我们需要确认当执行一次Release构建的时候是否成功,接下来在build.gradle中添加这几行:

代码语言:javascript复制
tasks.whenTaskAdded { theTask ->
       if (theTask.name.equals("packageRelease")) {
           theTask.dependsOn "getReleasePassword"
       }
}

这段代码Hook进了Gradle,并且在运行的时候Android Plugin会把闭包加入到Dependency Graph中。直到packageRelease执行之前,password都不是必须的,所以我们需要确保packageRelease任务依赖于我们的getReleasePassword任务。我们不能直接使用packageRelease.dependsOn()的原因是Android Plugin会基于Build Variant动态的生成打包的Tasks。这也就意味着,packageRelease任务直到Android Plugin扫描完所有的Build Variants之前,都不会存在。而发现的过程在Build之前就已经开始了。

在添加了Task的构建Hook之后,执行gradlew assembleRelease任务的结果如下:

Hook Android Plugin

就像上面截图所示,private.properties文件不可用,所以task在console中询问password。这种情况下,我们需要添加一些提醒,如何创建properties文件,并且添加password属性让未来的构建更贱简单。一旦我们的Task选择了keystore的密码,Gradle就可以开始打包我们的APP并且完成构建了。

为了让这个Task可以正常运转,它本质就是Hook到Gradle和Android Plugin中。

Hooking into the Android plugin

当开发Android App的时候,我们希望修改的任务大多都是与Android Plugin相关的。之前的例子,我们可以看到如何在一个自定义的Task中添加依赖。在这一届,我们来看看如何进行Android特殊的构建Hook。

一种Hook到Android Plugin的方法是操作Build Varian。我们只需要在遍历Variant的时候,完成我们的任务即可。

代码语言:javascript复制
android.applicationVariants.all { variant ->
     // Do something
}

为了得到所有的Build Variants,我们可以使用applicationVariants对象。一旦我们引用到了一个具体的Build Variant,我们就可以访问它的属性,并且操作它的属性,比如说名字,描述等等。如果你希望在Android Library中加入相同的逻辑,那么使用libraryVariants来替代applicationVariants即可。

这种Hook可以用来修改APK的名字,并且在文件名后添加版本号。这样可以更简单的生成一个带版本的APK名,而不需要手动修改文件名。接下来则看看如何实现

Automatically renaming APKs

在打包完后,我们来重命名APK。我们可以遍历App的Build Variants,并且修改outputFile属性。如下代码所示:

代码语言:javascript复制
android.applicationVariants.all { variant ->
     variant.outputs.each { output ->
        def file = output.outputFile
        output.outputFile = new File(file.parent,file.name.replace(".apk", "-${variant.versionName}.apk"))
      } 
}

每个Build Variant的输出都是一个APK文件。variant.outputs对象都会有一个属性名为outputFile,而它则是File类型的。一旦我们知道了output的路径后,我们就可以操纵它了。

如上所示,我们在文件名中添加了版本号,而APK的名字也会从app-debug.apk修改为app-debug-1.0.apk。接下来,我们来看看如何为每一个Build Variant创建一个Task。

Dynamically creating new tasks

由于Gradle工作方式以及Tasks的构建,我们可以在Configuration阶段基于Build Variant创建我们自己的Task。

为了解释这个强大的概念,我们会创建一个Task,但不是安装,而是运行Android App的某一个Build Variant。Install Task只是Android Plugin中的一部分,但是如果你通过命令行的installDebug任务安装了Apk的话,当安装完成后,需要手动启动App才行。而我们创建的这个Task则会把最后一步去掉。

通过Hook Application Variant中的属性:

代码语言:javascript复制
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."
          } 
       }
}

对于每个Variant,我们检查它是否有install这个任务。因为我们需要依赖install任务,所以必须要检查这个任务是否存在。一旦我们确定了install任务存在,我们就可以创建一个新的Task,并且基于Variant的名字赋予这个Task名字。我们需要将我们新建的任务依赖variant.install。这会在我们的任务执行前打开install任务。而在tasks.create()的闭包中,我们添加了一个description,可以帮助我们在执行gradlew tasks的时候展示日志。

在添加完description之后,我们也会添加真正的Task Action。在这个例子中,我们希望启动APP。你可以通过Android Debug Tool(ADB)在已经连接的设备或者模拟器中启动APP。

代码语言:javascript复制
$ adb shell am start -n com.package.name/com.package.name.Activity

Gradle有一个函数叫做exec(),这个函数可以让我们在命令行执行命令。为了确保exec()可以正常工作,我们需要提供一个可执行的环境变量。我们也需要传递一些参数,例如:

代码语言:javascript复制
doFirst {
       exec {
           executable = 'adb'
           args = ['shell', 'am', 'start', '-n',"${variant.applicationId}/.MainActivity"]
       }
}

为了得到包名,我们使用Build Variant中带有后缀的Application ID。而如果我们加了后缀,Activity的classpath仍然相同。例如:

代码语言:javascript复制
android {
       defaultConfig {
           applicationId 'com.gradleforandroid'
       }
       buildTypes {
           debug {
               applicationIdSuffix '.debug'
           }
       }
}

包名为com.gradleforandroid.debug,但是Activity的路径还是com.gradleforandroid.Activity。为了保证我们得到正确的Activity类,我们从ApplicationId中带入后缀:

代码语言:javascript复制
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]
        } 
}

首先,我们创建了一个变量为classpath,该值为applicationId。然后我们通过buildType找到后缀。在Groovy中,我们可以通过-=运算符来从String中减去一个String。这些修改可以保证在安装过后,使用后缀的APP也不会打开失败。

Creating your own plugins

如果你有一系列的Gradle的Tasks希望在多个Project中重用,那我们可以考虑把这些Task添加到一个自定义的插件中去。这样可以让我们自己的构建逻辑与别人共享。

Plugin也可以使用Groovy编写,Java或者Scala也都可以,只要是基于JVM的语言都可以。实际上,大部分的Android Plugin都是Java与Groovy混编的。

Creating a simple plugin

为了从已经保存到build.gradle中的构建逻辑提取出来,我们可以在build.gradle中创建一个Plugin。这是最简单的方法。

为了创建一个Plugin,我们需要创建一个新的Class,实现Plugin接口。我们也将使用我们之前动态创建Tasks的代码。我们的Plugin类如下:

代码语言:javascript复制
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会在插件使用的时候,调用这个函数。Project对象会作为参数传递,并且可以在Plugin中配置该project对象,并且使用它的函数以及属性。

在之前的例子中,我们需要首先需要访问project对象,需要注意我们需要在build.gradle中Apply这个Plugin才行,否则会导致异常。

为了保证这个Plugin在我们的构建配置中被Apply,需要在build.gradle中添加以下这一行:

代码语言:javascript复制
apply plugin: RunPlugin
Distributing plugins

为了发布一个Plugin,我们需要把它移动到一个单独的Module或者Project中。一个单独的Plugin拥有它自己的build.gradle文件来配置dependencies。这个Module会产生一个Jar文件,包括包含了Plugin的classes和属性。我们可以使用这个JAR文件将插件应用到多个模块和项目中,并与其他模块共享。而Gradle工程,则需要创建一个build.gradle文件进行配置:

代码语言:javascript复制
apply plugin: 'groovy'

dependencies {
       compile gradleApi()
       compile localGroovy()
}

一旦使用Groovy写Plugin后,我们就需要应用groovy这个插件。Groovy Plugin集成自Java Plugin,并且能让我们构建以及打包Groovy的类。Groovy和Java都可以支持,所以我们可以混编。你甚至可以使用Groovy来继承一个Java类。甚至你都感觉不到在使用Groovy。

为了开始我们单独模块的代码,我们首先需要保证正确的目录结构:

代码语言:javascript复制
plugin
   └── src
       └── main
           ├── groovy
           │    └─com
           │       └─package
           │            └── RunPlugin.groovy
           └── resources
               └── META-INF
                   └── gradle-plugins

对于任意的Gradle模块,我们需要提供一个src/main目录。因为这是Groovy工程,main的子目录使用groovy来替代java。而另外一个子目录叫做resources,用来指定我们Plugin的属性。我们创建了一个文件名为:RunPlugin.groovy在``package```目录下,而这个目录下我们会定义我们Plugin的类:

代码语言:javascript复制
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能够查找到这个Plugin,我们需要提供一个properties的文件。并且将该文件放到src/main/resources/META-INF/gradle-plugins/这个目录下。这个文件的名字需要匹配Plugin的ID。例如:RunPlugin,这个文件名称就叫做com.gradleforandroid.run.properties,该文件的内容为:

代码语言:javascript复制
implementation-class=com.gradleforandroid.RunPlugin

这个properties文件中唯一的东西就是包名以及Plugin具体实现的类名。当Plugin和Properties文件准备完成,我们就可以通过gradlew assemble命令来构建Plugin了。这会在构建的output目录下创建一个Jar文件。如果你希望把这个插件发布到Maven仓库上的话,你需要应用Maven Plugin

代码语言:javascript复制
apply plugin: 'maven'

然后配置uploadArchives任务:

代码语言:javascript复制
uploadArchives {
       repositories {
           mavenDeployer {
             repository(url: uri('repository_url'))
            }
        }
}

uploadArchives是一个已经定义过的Task。一旦你配置了任务中的仓库,你就可以执行它发布你的Plugin。

Using a custom plugin

为了使用一个Plugin,我们需要在buildscript中添加它作为dependency。首先,我们需要配置一个新的repository。这个配置决定了Plugin如何被共享。然后,我们需要在dependencies中配置Plugin的classpath。如果你想包含一个Jar文件的话,我们可以定义flatDir仓库:

代码语言:javascript复制
buildscript {
       repositories {
           flatDir { dirs 'build_libs' }
       }
       dependencies {
           classpath 'com.gradleforandroid:plugin'
       } 
}

如果我们已经在Maven或者Ivy仓库中上传了该插件的话,那么它就会有一点不一样。在我们设置了dependency之后,我们就可以应用该Plugin了:

代码语言:javascript复制
apply plugin: com.gradleforandroid.RunPlugin

当使用了apply()方法,Gradle会创建一个Plugin的实例,然后执行Plugin的apply()方法。

0 人点赞