美文网首页
Gradle for Android(七) 创建任务和插件

Gradle for Android(七) 创建任务和插件

作者: sollian | 来源:发表于2017-08-29 10:27 被阅读238次

    目前为止,我们已经学习了如何修改Gradle构建属性,以及如何运行任务。本章,我们会深入了解这些属性,并且创建我们自己的任务。一旦我们学会了创建任务,就可以更进一步,学习如何创建Gradle插件。

    在学习创建任务之前,我们需要学习一些重要的Groovy概念。因为了解一些Groovy基础有助于我们自定义任务和插件。学习Groovy也可以帮助我们了解Gradle的原理,以及构建文件的书写格式。

    本章内容有:

    • 了解Groovy
    • 学习任务
    • 接入Android插件
    • 创建插件

    了解Groovy

    由于多数Android开发者精通Java,所以将Groovy和Java对比学习相对要容易一些。Groovy对于Java开发者来说很容易阅读,但如果不简单了解一下的话,编写起来还是有一定难度的。

    使用Groovy Console来学习Groovy是一个很好的途径。这个应用包含在Groovy SDK中,可以立即执行Groovy代码得到结果。Groovy Console也可以运行纯Java代码,可以更加方便的对比Java和Groovy代码。你可以从http://groovy-lang.org/download.html下载带有Groovy Console的Groovy SDK。

    简介

    Groovy源自Java,并在JVM上运行。Groovy是一种简单、直接的语言,既是脚本,也是一种成熟的编程语言。本节我们通过对比来了解Groovy的原理以及和Java的不同。

    Java打印字符串代码如下:

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

    而Groovy如下:

    println 'Hello, world!'
    

    两者有几个关键的不同之处:

    • 没有System.out命名空间
    • 方法的参数没有加圆括号
    • 语句末尾没有分号

    示例同样在字符串的两侧使用了单引号。你也可以使用双引号,但两者作用不完全一样。双引号的字符串可以包含插值表达式。插值是计算包含占位符的字符串的过程,并将占位符替换为真实值。占位符表达式可以是变量或者方法。包含方法或者多个变量的占位符表达式需要放入{}中,并以$作为前缀;仅包含单个变量的占位符表达式只需要以$为前缀。下面是一些例子:

    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."。

    字符串插值也可以允许你动态执行代码,如下:

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

    如果你已经习惯了java,这看起来会很陌生,但这是动态编程语言的正常的语法和行为。

    类和成员

    Groovy创建类和Java很相似。下面是包含一个成员的类:

    class MyGroovyClass {
        String greeting
    
        String getGreeting() {
            return 'Hello!'
        }
    }
    

    注意类和成员都没有明确的访问修饰符。Groovy默认的访问修饰符和Java不一样。类和方法默认是public的,成员默认是private的。

    下面创建MyGroovyClass的实例:

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

    你可以使用def关键字来创建新的变量。在有了类的示例之后,你就可以操作类成员了。Groovy会自动添加成员的访问器。你也可以覆写它们,比如这里我们覆写了getGreeting()方法。

    你也可以直接调用成员,这实际也是在调用getter方法。也就是说你可以输入instance.greeting来代替instance.getGreeting()

    println instance.getGreeting()
    println instance.greeting
    

    两种方式结果一样。

    方法

    和变量相似,你不需要为方法指定返回类型。你可以这么做,哪怕只是为了简洁。Groovy和Java方法的另一个不同之处是最后一行默认是返回值,即使没有使用return关键字。

    为了展示Java和Groovy方法的不同,参照下面的Java方法:

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

    你需要指定方法的访问类型、返回值类型、参数类型,并在最有一行使用return关键字。

    在Groovy中,这个方法如下:

    def square(def num) {
        num * num
    }
    square 4
    

    返回值类型和参数类型不必指明。def关键字取代了明确的类型,返回值不需要return关键字。尽管如此,为了清晰,还是推荐使用return关键字。调用方法时,你不必为它添加圆括号。

    下面是Groovy更简洁的定义方法的形式:

    def square = { num ->
        num * num
    }
    square 8
    

    这不是一个常规的方法,而是一个闭包。Java没有类似闭包的概念。闭包在Groovy和Gradle中有非常重要的作用。

    Closures(闭包)

    闭包是一个可以有输入和输出的匿名代码块。它可以用来给变量赋值,也可以作为方法的参数。

    你可以在花括号中添加代码块来定义简单的闭包。如果你想更明确一点,你可以将类型添加到定义中:

    Closure square = {
        it * it
    }
    square 16
    

    Closure显示指明了代码块的类型为闭包。前面的示例还介绍了隐式无类型参数it的概念。如果你没有为闭包显示添加参数,Groovy会自动添加一个,参数名为it,你可以在所有闭包中使用它。如果调用者没有指定任何参数,it的值为null。这使代码稍微简洁一些,但只在有单个参数时有效。

    在Gradle中,我们每时每刻都在使用闭包。本书中,目前为止我们说的块就是指闭包。也就是说,android块和dependencies块都是闭包。

    集合

    在Gradle中使用Groovy时,有两个集合类型:list和map。

    Groovy创建list非常简单。只需要:

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

    list的遍历也非常简单。可以使用each方法:

    list.each() { element ->
        println element
    }
    

    你也可以使用it来精简代码:

    list.each() {
        println it
    }
    

    map在Gradle的一些配置和方法中使用。map是一个键值对的集合,可以如下定义:

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

    可以使用get方法或者方括号来访问map。

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

    Groovy还有一个更加简单的方式。可以直接使用map.key的语法来得到相应的值:

    pizzaPrices.pepperoni
    

    Groovy in Gradle

    现在我们已经了解了Groovy的基础,是时候回过头来重新看下Gradle的构建文件了。我们可以比较容易地理解配置语法。比如,Android插件是如何被引入构建的:

    apply plugin: 'com.android.application'
    

    这块代码使用了Groovy的简写。完全不简写的话,它是这个样子的:

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

    这样理解起来局容易多了。applyProject类的一个方法,接收一个map参数,其中键为plugin,值为com.android.application

    另一个例子是dependencies块:

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

    现在我们知道了这个块是一个闭包,作为Projectdependencies()方法的参数。这个闭包会传递给DependencyHandler对象,该对象有add()方法。这个方法由三个参数:一个定义配置的字符串,一个定义依赖符号的对象,一个包含这个依赖的属性的闭包。它的全写如下:

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

    到现在为止你应该已经可以看懂这些配置文件了。

    如果你想学习更多Gradle是如何使用Groovy的,你可以从看Project的官方文档看起,地址为: http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html

    学习任务

    自定义Gradle任务可以显著提高开发者的效率。任务可以操作已有的构建过程,添加新的构建阶段,或者影响构建的输出。你可以执行简单的任务,比如重命名生成的APK。任务可以使你运行更为复杂的代码,比如在app打包前,生成几个不同密度的图像。在学会创建任务之后,你就有能力去改变构建过程的方方面面。当你学习如何使用Android插件时,这一点尤其正确。

    定义任务

    任务属于一个Project对象,每个任务都实现了Task接口。最简单的定义任务的方法是执行task方法,并将任务名称传递给它:

    task hello
    

    这就创建了一个任务,但是执行它不会做任何事情。想要创建一个稍微有点作用的任务,你需要为它添加动作。初学者常犯的一个错误是这样创建任务:

    task hello {
        println 'Hello, world!'
    }
    

    执行的结果如下:

    $ gradlew hello
    Hello, world!
    :hello
    

    从结果看,你可能认为这个任务做事情了,但实际上,"Hello,world!"是在任务执行之前打印出来的。为了理解这个地方发生了什么,我们需要回过头来看下基础的东西。在第一章,我们学习了Gradle构建的生命周期:初始化阶段、配置阶段和执行阶段。在你像上例中那样添加任务时,你实际上设置了任务的配置。即使你执行其他的任务,"Hello,world!"也会打印。

    如果你想在执行阶段为任务添加动作,使用如下方式:

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

    唯一的区别是闭包前的<<符号。这会告知Gradle这段代码是为执行阶段准备的,而不是配置阶段。

    为了展示它们的区别,看下面的构建文件:

    task hello << {
        println 'Execution'
    }
    
    hello {
        println 'Configuration'
    }
    

    我们定义了任务hello,在执行时会打印"Execution"。我们也为hello任务的配置阶段定义了代码,即打印"Configuration"。即使配置块是在真正的任务定义代码之后定义的,它也会先被执行。上例的输出为:

    $ gradlew hello
    Configuration
    :hello
    Execution
    

    错误的使用配置阶段导致任务失败是一个很常见的错误。在创建任务时需要牢记。

    由于Groovy有很多简写,Gradle定义任务有如下几种形式:

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

    前两个块是Groovy实现相同功能的两种不同方式。你可以使用圆括号,但不是必须的。你也可以不用单引号。在这两个块中,我们调用了task()方法,它需要两个参数:一个任务名称和一个闭包。task()方法是Project类的方法。

    最有一个块没有使用task()方法,而是使用了Project类的tasks对象,该对象是TaskContainer的实例。这个类提供了一个create()方法,接收一个Map和一个闭包作为参数,返回一个Task

    使用简写是非常便捷的书写方式,很多在线的示例和教程都是使用的简写。而全写对初次学习非常有用,这样,Gradle才看起来不那么神奇,理解起来更加容易。

    任务剖析

    Task接口是所有任务的基础,定义了属性和方法的集合。所有这些被DefaultTask类实现。这是一个标准的任务类型的实现,在你定义一个新的任务时,它是基于DefaultTask的。

    从技术上讲,DefaultTask并不是真正的Task接口所有方法的实现类。Gradle有一个名为AbstractTask的内部类,包含所有方法的实现。因为AbstractTask是内部类,我们不能继承它。所以我们才关注继承自AbstractTaskDefaultTask类,并覆写它。

    每个任务有一个Action对象的集合。在执行任务时,所有的动作会被顺序执行。你可以使用doFirst()doLast()方法添加动作。这两个方法都以闭包作为参数,然后将其封装成一个Action对象。

    你只有使用doFirst()或者doLast()方法才能为任务添加在执行阶段运行的代码。我们之前使用的左移操作符<<doFirst()方法的简写。

    下面是使用这两个方法的例子:

    task hello {
        println 'Configuration'
    
        doLast {
            println 'Goodbye'
        }
    
        doFirst {
            println 'Hello'
        }
    }
    

    执行hello任务,输出如下:

    $ gradlew hello
    Configuration
    :hello
    Hello
    Goodbye
    

    你也可以多次调用doFirst()doLast()

    task mindTheOrder {
        doFirst {
            println 'Not really first.'
        }
        doFirst {
            println 'First!'
        }
        doLast {
            println 'Not really last.'
        }
        doLast {
            println 'Last!'
        }
    }
    

    该任务的输出为:

    $ gradlew mindTheOrder
    :mindTheOrder
    First!
    Not really first.
    Not really last.
    Last!
    

    注意doFirst()总是将动作加到任务的开头,doLast()总是加到末尾。你在使用这样的方法时需要谨慎,尤其是对顺序有要求时。

    对于需要顺序执行的任务,你可以使用mustRunAfter()方法。这个方法允许你改变Gradle构建的依赖图。

    task task1 << {
        println 'task1'
    }
    
    task task2 << {
        println 'task2'
    }
    
    task2.mustRunAfter task1
    

    同时执行两个任务,task1总会在task2之前运行:

    $ gradlew task2 task1
    :task1
    task1
    :task2
    task2
    

    mustRunAfter()方法并没有在任务间添加依赖,你仍然可以单独执行task2,而不必执行task1。如果你需要让一个任务依赖另一个任务,可以使用dependsOn()方法。mustRunAfter()dependsOn()的区别可以从下面的示例提现:

    task task1 << {
        println 'task1'
    }
    
    task task2 << {
        println 'task2'
    }
    
    task2.dependsOn task1
    

    单独执行task2,得到的结果如下:

    $ gradlew task2
    :task1
    task1
    :task2
    task2
    

    使用mustRunAfter(),在同时执行task1、task2时,task1总是在task2之前执行,但是它们仍然可以单独执行。而使用dependsOn(),单独执行task2总会先触发执行task1。这是一个明显的不同之处。

    使用任务来简化发布过程

    在你将应用发布到Google Play商店之前,你需要用证书进行签名。你需要有自己的keystore,它包含一组私钥。在你有了keystore和私钥之后,可以在配置文件中定义签名配置:

    android {
        signingConfigs {
            release {
                storeFile file("release.keystore")
                storePassword "password"
                keyAlias "ReleaseKey"
                keyPassword "password"
            }
        }
    
        buildTypes {
            release {
                signingConfig signingConfigs.release
            }
        }
    }
    

    这种方法的缺点是,你的keystore的密码以纯文本的方式保存在仓库中。如果你是在做开源项目,这是绝对不行的;任何一个有keystore文件和私钥的人可以用你的身份发布应用。为了防止这种情况的发生,你需要创建一个任务,在每次生成发布包时,查询发布密码。虽然这有点麻烦,但它确实能使构建服务器自动生成发布版本。一个不错的保存keystore密码的解决方案是创建一个不保存在仓库中的配置文件。

    在项目的根目录创建一个private.properties文件,添加如下代码:

    release.password = thepassword
    

    我们假设keystore和key本身的密码相同。如果你的密码不同,也可以添加第二个属性。

    设置好这些,你可以定义一个getReleasePassword的任务:

    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的文件。如果文件存在,该任务会加载所有的属性信息。properties.load()方法会查询键值对,比如我们定义的release.password

    为了确保任何人在没有private.properties文件或者属性文件存在,但没有release.password属性的情况下都可以运行脚本,需要添加一个反馈。如果密码为空,在控制台要求输入密码:

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

    Groovy检查字符串是否为空是非常简单的。password?.trim()中的问号是一个null检查,在password为null时,不会调用trim()方法。我们不用进行明确的空判断,因为空的字符串在if语句中会返回false

    new String()是必须的,因为System.readPassword()会返回一个字节数组,我们需要显示转换为字符串。

    在有了keystore的密码之后,我们就可以为release版本配置签名配置了。

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

    任务写完了,我们还要确保被执行。在build.gradle文件中添加如下代码:

    tasks.whenTaskAdded { theTask ->
        if (theTask.name.equals("packageRelease")) {
            theTask.dependsOn "getReleasePassword"
        }
    }
    

    这段代码会将一个闭包与Gradle和Android插件挂钩,在任务检入依赖图时,代码会被执行。packageRelease执行之前,不会要求密码,所以我们确保packageRelease依赖getReleasePassword任务。我们不可以仅仅使用packageRelease.dependsOn(),因为打包任务是Android插件动态生成的,依赖于构建变体。也就是说,在Android插件查询到所有的构建变体之前,packageRelease任务是不存在的。这个查找过程在构建之前开始。

    想要这个任务工作,跟Gradle和Android插件挂钩是必须的。这是一个强有力的概念,我们会探索更多的细节。

    与Android插件挂钩

    在Android开发中,我们想要影响的多数任务都和Android插件相关。可以通过挂钩构建过程来增加任务的行为。上例我们学习到了在常规构建中,怎样向一个Android任务添加新的依赖任务。本节,我们将研究一些Android特有构建的钩子。

    一个挂钩Android插件的方法是修改构建变体。这么做非常直接,你只需要用下面的代码片段就可以遍历所有的构建变体:

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

    你可以使用applicationVariants对象来获取构建变体的集合。拿到构建变体的引用后,你就可以访问和更改它的属性了,比如name,description等。如果你想在Android library上使用相同的逻辑,可以使用libraryVariants对象。

    注意我们遍历构建变体时,使用的是all()而不是each()方法。因为each()的触发是在evaluation阶段,在Android插件创建构建变体之前。all()方法在每次集合加入新元素时就会触发。

    这个钩子可以用来在APK保存之前修改名称,或者在文件名中添加版本号。这就使维护APK变得容易,因为你不需要手动编辑文件名。下一节我们会学习如何实现它。

    自动重命名APK

    一个常见的修改构建构成的应用场景是在APK打包后,重命名以包含版本信息。你可以遍历应用的构建变体,修改outputFile属性来达到目的:

    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"))
        }
    }
    

    每个构建变体有一个输出的集合。一个Android应用的输出是一个APK。每个输出对象有一个File类型的属性outputFile。知道了输出的路径之后,你就可以修改它。本例中我们在文件名中加入了版本信息。

    强大的Android插件钩子和简洁的Gradle任务的结合给了我们无限的可能。下一节,我们学习如何为应用的每个构建变体创建一个任务。

    动态创建任务

    基于Gradle的工作方式和任务的构造方式,在Android构建变体的基础上,我们可以在配置阶段创建新的任务。为了展示这个强大的概念,我们将创建一个任务,用来安装、运行Android应用的任何一个构建变体。install任务是Android插件定义的,但是如果你是用命令行运行installDebug任务来安装应用,安装结束后,你需要手动启动它。本节我们将创建的任务会解决最后一步的问题。

    首先与applicationVariants属性挂钩:

    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."
            }
        }
    }
    

    对每一个变体,检查它是否有install任务。这是必需的,因为我们要创建的新的任务需要依赖于它。确定install任务存在后,我们基于变体的名称创建一个新任务,该任务依赖于variant.install,执行时会触发install任务。task.create()的闭包中添加了描述字段,在执行gradlew tasks时会显示出来。

    除了添加描述外,我们还需要添加任务执行的动作。本例中,我们想要启动应用。Android Debug Tool(ADB)提供了启动真机或者模拟器应用的命令:

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

    Gradle有一个exec()的方法,可以执行命令行。为使exec()工作,我们需要加到PATH环境变量中。我们同样需要把所有的参数传递给args属性:

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

    为了获取包名,可以使用构建变体的applicationId,它可能包含一个后缀(如果提供的话)。在这个例子中,后缀会有一个问题。即使我们添加了后缀,activity的路径也是不会变的,比如:

    android {
        defaultConfig {
            applicationId 'com.gradleforandroid'
        }
    
        buildTypes {
            debug {
                applicationIdSuffix '.debug'
            }
        }
    }
    

    包名是com.gradleforandroid.debug,但是activity的路径依然是com.gradleforandroid.Activity。为了得到正确的路径,我们需要去掉后缀:

    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。接下来,查找后缀。在Groovy中,字符串可以使用-做减法。这些更改确保在添加后缀的情况下,应用安装完成后可以自动运行。

    创建插件

    如果你有一些想要在多个工程中复用的任务,可以考虑将任务提取到自定义的插件中。这样你就可以复用构建逻辑,或者将它分享出去。

    插件可以由Groovy编写,也可以基于JVM的其它语言,比如Java、Scala。实际上,Gradle Android插件的大部分是有Java和Groovy混合编写的。

    创建一个简单的插件

    为了提取构建文件中的构建逻辑,你可以在build.gradle文件中创建一个插件。这是最简单的自定义插件的方式。

    为了创建插件,首先需要创建一个实现了Plugin接口的类。我们将使用本章已写过的动态创建run任务的代码。插件类如下:

    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会作为参数传递过来,这样插件就可以配置项目或者使用项目的方法和属性。在上一个例子中,我们不能从Android插件直接调用属性,而是应该首先访问project对象。需要注意的是,这要求Android插件在我们的插件之前被引入到项目中,否则,project.android会引发异常。

    任务的代码和之前是一样的,只是exec()需要用project.exec()替换。

    将该插件引入到构建文件中:

    apply plugin: RunPlugin
    

    分发插件

    为了分发插件并分享给其他人,你需要将它移动到一个单独的模块(或项目)中。单独的插件有它自己的构建文件来配置依赖和分发的方法。这个模块生成一个JAR文件,包含插件类和属性。你可以使用这个JAR文件将插件应用到模块或者项目中,也可以分享给他人。

    和其他Gradle项目一样,创建一个build.gradle来配置构建:

    apply plugin: 'groovy'
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    

    由于我们是用Groovy写的插件,所以需要引入Groovy插件。Groovy插件继承自Java插件,使我们可以构建和打包Groovy类。Groovy同样支持Java,你可以混合使用。你甚至可以使用Groovy扩展Java类。这使Groovy很容易上手。

    gradleApi()依赖用于在我们的插件中访问Gradle的命名空间。
    localGroovy()依赖是Groovy SDK在Gradle中安装的发行版。
    这两个依赖是Gradle自动添加的。

    如果你计划公开发布你的插件,请确保在构建文件中定义了group和version信息,例如
    group = 'com.gradleforandroid'
    version = '1.0'

    为了使用独立模块的代码,我们还需要确保正确的目录结构:

    plugin
    └── src
         └── main
             ├── groovy
             │    └── com
             │        └── package
             │             └── name
             └── resources
                  └── META-INF
                      └── gradle-plugins
    

    和其他的Gradle模块一样,我们需要提供一个src/main目录。因为这是一个Groovy工程,所以main的子目录是groovy而不是java。main的另一个子目录是resources,用来指定插件的属性。

    我们在包目录创建了一个RunPlugin.groovy的类文件:

    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,它的内容是:

    implementation-class=com.gradleforandroid.RunPlugin
    

    属性文件内容仅仅是实现了Plugin接口的类的包名和类名。

    准备好接口和属性文件后,我们可以使用gradlew assemble命令来构建插件,这会在构建输出目录创建一个JAR文件。如果你想将插件上传到Maven仓库,你首先需要引入Maven插件:

    apply plugin: 'maven'
    

    然后,你需要配置uploadArchives任务:

    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: uri('repository_url'))
            }
        }
    }
    

    uploadArchives是预定义的任务。为这个任务配置了仓库之后,你就可以运行它来发布你的插件。本书不会讲述如何设置Maven仓库。如果你想让你的插件公开可用,可以发布到Gradleware的插件门户(https://plugins.gradle.org)。插件门户有一大批Gradle插件(不仅仅是Android的),如果你想扩展Gradle的默认行为,可以去浏览一下。你在https://plugins.gradle.org/docs/submit网站可以学习如何发布插件。

    为自定义插件写测试不是本书的内容,如果你想公开发布插件,强烈建议进行测试。你可以在Gradle用户指南https://gradle.org/docs/current/userguide/custom_plugins.html#N16CE1学习编写测试

    使用自定义插件

    要使用插件,需要在buildscript块将其添加为依赖。首先,我们需要配置一个新仓库。仓库的配置依赖于插件发布的途径。其次,我们需要在dependencies块配置插件的类路径。

    如果你想引入上例创建的JAR文件,可以定义一个flatDir仓库:

    buildscript {
        repositories {
            flatDir { dirs 'build_libs' }
        }
    
        dependencies {
            classpath 'com.gradleforandroid:plugin'
        }
    }
    

    如果已经将插件上传到Maven或者Ivy仓库,配置会有所不同。我们在第3章讲了配置管理,所以我们将不再讲述不同之处。

    配置好之后,我们需要应用插件:

    apply plugin: com.gradleforandroid.RunPlugin
    

    在使用apply()方法的时候,Gradle会创建一个插件类的实例,并执行插件自己的apply()方法。

    相关文章

      网友评论

          本文标题:Gradle for Android(七) 创建任务和插件

          本文链接:https://www.haomeiwen.com/subject/qlagrxtx.html