前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gradle For Android(7)--创建Task以及Plugin

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

作者头像
None_Ling
发布2018-10-24 15:12:42
1.8K0
发布2018-10-24 15:12:42
举报
文章被收录于专栏:Android相关Android相关

介绍

到目前为止,我们已经看到了很多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()方法。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.10.22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • Understanding Groovy
    • Classes and members
      • Methods
    • Closures
      • Collections
        • Groovy in Gradle
          • Getting started with tasks
            • Defining tasks
            • Anatomy of a task
            • Using a task to simplify the release process
            • Hooking into the Android plugin
            • Automatically renaming APKs
            • Dynamically creating new tasks
          • Creating your own plugins
            • Creating a simple plugin
            • Distributing plugins
            • Using a custom plugin
        相关产品与服务
        持续集成
        CODING 持续集成(CODING Continuous Integration,CODING-CI)全面兼容 Jenkins 的持续集成服务,支持 Java、Python、NodeJS 等所有主流语言,并且支持 Docker 镜像的构建。图形化编排,高配集群多 Job 并行构建全面提速您的构建任务。支持主流的 Git 代码仓库,包括 CODING 代码托管、GitHub、GitLab 等。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档