Android 进阶——自定义Gradle插件的三种方式初探与上传至本地仓库完全攻略(一)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/CrazyMo_/article/details/89070618

引言

对于我们Android开发来说,尤其是使用Android Studio 进行开发来说对于插件(此处的插件特指Gradle插件而不是Inteiilj IDEA 使用的插件,前者运行在Gradle构建系统之上,而后者则是运行于IDEA 上)绝不会陌生,是否曾觉得自定义插件很深奥,接下来我们就层层剥壳,逐步揭开插件的神秘面纱…

一、Gradle插件Plugin概述

按照官方文档描述,Gradle 插件可以把那些可重用的构建逻辑的片段打包起来,以供其他项目复用,简而意之,Gradle插件的作用就是改变默认的Gradle构建流程并打包供其他项目复用,理论上Gradle 支持你使用任何最终会被编译成JVM 字节码的语言,Groovy、Java或Kotlin都是不错的选择,因为Gradle API可以与这些语言无缝对接,一般使用Java或Kotlin实现的静态类型的插件将比使用Groovy实现的相同插件执行得更好,其实很多情况下插件最终会被打包成一个jar包,加入到Gradle的构建流程中。在Gradle中有两种类型的插件:

  • 脚本插件——脚本插件是一个额外的构建脚本,它提供了一种声明性方法来操作构建,通常在构建中使用

  • 二进制插件——实现插件接口并采用编程方法来操作构建的类,二进制插件可以驻留在插件JAR中的一个构建脚本和项目层次结构或外部

二、开发Gradle自定义插件的三种方式

开发一个Gradle插件,其实很简单,从总体上来说就是需要创建一个Groovy\Java\Kotlin Module作为插件源码项目,然后在项目中编码即可,而创建插件源码项目有三种主要方式:

1、buildSrc project方式创建二进制插件

直接在Android Studio中Project 目录下右键New——>Directory 并命名为buildSrc然后再后在src/main中建立两个目录:一个就是存放代码的groovy目录,一个是存放自定义插件名称的resources目录(结构形如rootProjectDir/buildSrc/src/main/groovy 或者 rootProjectDir/buildSrc/src/main/java 或者 rootProjectDir/buildSrc/src/main/kotlin ),这是个特殊的目录,可以理解成预置的源码目录,因为在这个目录中Gradle已经为我们自动引入了Groovy、Java、Gradle的Api,Gradle 将负责编译和测试插件并使其当前工程里所有Module 的构建脚本中可用(欲知了解详情
通过buildSrc 方式创建的Gradle插件也是只能在我们项目中进行使用,不好复用,当buildSrc 存在时,执行编译任务的时候Gradle 会自动先构建buildSrc 里的所有的任务(无论工程中有多少子Module,都会先执行buildSrc 里的任务),创建并使用插件主要步骤:

  • 创建自定义插件类实现Plugin< Project > 接口并重写apply方法
  • 根据业务在apply方法中实现插件真正要做的事:定义扩展属性创建自定义任务设置任务之间的逻辑关系等等。
  • 在build.gradle脚本中使用 apply plugin: 插件类名 ,引入自定义插件

一般说来一个Gradle插件由以下主要元素构成:扩展属性自定义方法自定义任务而把三者关系梳理好并设置对应的逻辑就可以改成为一套完整的插件,从而改变构建流程了。接下来以定义一个与Google的Android Gradle插件结构类似的插件(仅仅是结构类似的),最终buildSrc项目结构及使用配置效果图如下
在这里插入图片描述
的为例进一步揭开插件的本质:

1.1、定义扩展属性及方法

Gradle API 提供了一系列创建属性的方法,实现时只需要传入相应的键值对即可,在本例中提供了两个扩展属性,而通常一个属性最终映射为一个类,所以可能需要两个类。

//Android2.groovy
class Android2{
    //在Groovy中定义了属性,可以把def 看成是任意类型的,也可以直接使用具体类型声明,编译为字节码时,会自动添加上构造函数和getter和setter方法,具体可以参见Android2.class
    def compileSdkVersion

    def test(){
        println("自定义方法被执行")
    }

    def test(String params){
        println("自定义方法被执行带参数:"+params)
    }
}

//DefaultConfig.groovy
class DefaultConfig {
    def applicationId
    def minSdkVersion
}

在Groovy中定义了属性后,在编译为字节码时,会自动添加上构造函数和getter和setter方法,以Android2.groovy编译后的class文件为例:
在这里插入图片描述

1.2、自定义任务Task

Gradle的构建流程是由一系列任务去协作完成的,而各任务之间的执行逻辑顺序本质上就是由Gradle 提供的API 去控制的,不熟悉相关API的话建议先查阅文档或者阅读上一篇Android 进阶——Android Studio 项目结构详细述及自定义扩展属性、Gradle 任务及构建生命周期(五),无论是创建自定义任务还是设置逻辑关系本质上都是去调用Gradle 对应的API,创建成功之后会在对应Moudle下的Gradle Task视图下看到(默认为Other分组)

//HelloTask.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class HelloTask extends DefaultTask{
    @TaskAction
    void run(){
        println("HelloTask 里真正处理的核心逻辑")
    }
}

1.3、实现自定义插件主类,实现Plugin< Project > 接口并重写apply方法

所谓“自定义插件主类”这个说法或许不怎么严谨,不过呢可以把Plugin接口中的apply 方法看成是插件的入口,只要使用到插件就会自动触发apply方法的运行。

import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project

class AndroidExtension implements Plugin<Project>{

    @Override
    void apply(Project project) {
        println('引入AndroidExtensions 插件时,apply方法自动被执行')
        //创建扩展属性:android2,就可以获取对应的参数
        def andr =project.extensions.create("android2",Android2)
        //在Android2 扩展属性上创建扩展属性 defaultConfig
        andr.extensions.create("defaultConfig",DefaultConfig)
        ////project.android2.extensions.create("defaultConfig",DefaultConfig)
        //在Gradle分析之后,读取自定义插件配置的属性
        project.afterEvaluate(new Action<Project>() {
            @Override
            void execute(Project project2) {
                println("获取自定义andorid2的属性值:")
                println("android2.compileSdkVersion:"+project2.android2.compileSdkVersion)
                println("android2.defaultConfig.applicationId:"+project2.android2.defaultConfig.applicationId)
                //Todo 处理自定义插件的参数,和创建自定义的任务,至于怎么处理完全是业务方面的事了,本质上和Java 项目一样,创建一个文件还是进行通信获取,It's all depends on U
                //创建自定义任务并通过相关方法设置逻辑关系
                HelloTask task = project2.tasks.create('helloWorld',HelloTask)
                //在执行任务helloWorld之前需要先执行preBuild,当然也可以设置其他逻辑关系,
                task.dependsOn project2.tasks.getByName('checkDebugManifest')
                project2.tasks.getByName('preReleaseBuild').dependsOn task
            }
        })

/*        project.afterEvaluate {
            println("获取自定义andorid2的属性值:")
            println("android2.compileSdkVersion:"+project.android2.compileSdkVersion)
            println("android2.defaultConfig.applicationId:"+project.android2.defaultConfig.applicationId)
            //Todo 处理自定义插件的参数,和创建自定义的任务,至于怎么处理完全是业务方面的事了,本质上和Java 项目一样,创建一个文件还是进行通信获取,It's all depends on U
            //创建自定义任务并通过相关方法设置逻辑关系,在执行任务helloWorld之前需要先执行preBuild,当然也可以设置其他逻辑关系,
            HelloTask task = project.tasks.create('helloWorld',HelloTask)
            task.dependsOn project.tasks.getByName('checkDebugManifest')
            project.tasks.getByName('preReleaseBuild').dependsOn task
        }*/
    }
}

1.4、引入并使用插件

在build.gradle构建脚本中引入插件.

apply plugin: AndroidExtension

android2{
    test()
    test("Hello 带参数的方法")
    compileSdkVersion 28
    defaultConfig{
        applicationId "com.crazymo.android2"
        minSdkVersion 20
    }
}

直接在命令行使用gradlew assembleDebug或者通过指令gradle -q taskNamexx编译之后的运行效果如图:
在这里插入图片描述
buildSrc这个特殊的目录下,可以使用直接使用java api或者gradle 等有关API,原因在于在buildSrc中默认引入了Java插件、Groovy插件和Gradle依赖的支持,假如我们的自定义插件还需要依赖第三方库的话,也是支持的,只需要编写我们自己的build.gradle构建脚本(build.gradle这个文件只是默认的构建脚本的名称,你也可以指定其他名称的设置就好),然后通过配置引入第三方库即可,比如说引入android插件的支持只需要两步:

  • 创建自定义的构建脚本默认名为build.gradle
  • 在自定义构建监本中引入android插件的支持

最终buildSrc的源码目录如下:

在这里插入图片描述
而且buildSrc 模块在当前工程Project下所有子Module 可见

2、Build script方式创建脚本插件

所谓Build script方式,就是直接在.gradle脚本中实现插件,脚本插件可以从本地文件系统上的脚本或远程位置应用。文件系统位置相对于项目目录,而远程脚本位置指定HTTP URL,主要有两种形式:

2.1、直接在Android Studio项目模块下的app模块下的build.gradle脚本中编写插件的源码并使用

在这里插入图片描述

2.2、把插件定义到一个单独的.gradle脚本文件中再引入到要使用插件的build.gradle构建脚本中

在这里插入图片描述

无论哪一种形式,本质都是一样的,当项目编译的时候会自动使用Gradle插件,而不用再上传到本地仓库或者远程仓库,但是也因为在其他构建文件中不可见而导致不方便在其他项目中复用,因为要复用的话需要拷贝所有文件。

3、Standalone project方式创建二进制插件

前面两种方式在于给非当前工程重用方面,都不是最佳选项,实际开发中为了为了让其他项目便捷快速的重用,都会把插件源码上传到代码仓库中,这才是插件的正确打开方式,前面文章中也介绍了在Android Studio 使用的Gradle 构建系统的工作机制:当我们在构建脚本使用了apply plugin:com.android.application时,事实上Gradle 就会去Project下构建脚本的buildscript节点下配置的repositories中配置的仓库里的dependencies下的路径去寻找加载插件,这本质上就是将插件的jar包上传导对应的代码仓库中。如果我们的自定义插件也想要这样的形式,那么就需要使用第三种形式,建立一个独立的插件工程源码,并进行相关的配置: 在 main 目录下再新建resources 目录,在resources 目录下再新建 META-INF 文件夹,再新建文件夹gradle-plugins,最后在gradle-plugins目录下建立插件id对应的properties文件。,接下来我们以在Android Studio 里创建Java Module开发自定义插件为例总结下相关步骤:

3.1、创建一个普通的Module并引入Java插件和Groovy插件及有关的API

在这里插入图片描述

3.2、创建groovy目录并实现插件代码

在这里插入图片描述

3.3、创建在main\resources\META-INF\gradle-plugins目录下创建后缀为.properties文件,并指定引入时的key。

这个文件很重要,这个文件的名称就是所谓的插件Id,是**apply plugin:**pluginId时传入的。
在这里插入图片描述
使用Standalone project方式创建的插件,一般在使用前需要把之上传到本地代码仓库或者远程代码仓库,然后才能使用,通过配置来引入,具体步骤见下文。

二、上传至本地代码仓库

无论是上传插件源码、还是Android项目源码,核心步骤大同小异,核心思想都是先配置产出、再上传至库,区别在于根据不同的项目、不同的仓库位置选择不同的插件并根据各自的文档配置编译脚本即可。

1、引入maven-publish插件

由于这里我们要上传的Java项目,所以使用Gradle 官方的插件——maven或者maven-publish主要来帮助我们配置产出及将上传到仓库的产物。

在这里插入图片描述

2、 配置上传的内容信息

maven-publish插件提供了一个叫做publishing节点的属性([详情请见])(https://docs.gradle.org/current/userguide/publishing_maven.html),用于配置上传到仓库的产物(比如说配置配置插件库的的POM信息,用于拼接加载时对应的classpath),成功配置这个节点之后就会在Gradle Task视图栏自动创建了一组任务组,执行publishXxxPublishToMavenLocal任务上传到本地仓库即可(其中Xxx代表publication中的名称)

在这里插入图片描述
引入成功之后会在Gradle Task视图栏自动创建了一组任务组(其中PubName对应是pubilications 配置的名称):

  • generatePomFileForPubNamePublication (对应的类是GenerateMavenPom)— 生成默认的POM文件并保存在build / publications / $ pubName / pom-default.xml下。
  • publishPubNamePublishToMavenLocal(对应的类是 PublishToMavenLocal)——把配置的产出全部复制到本地代码仓库目录下。
  • publishToMavenLocal——依赖于publishPubNamePublishToMavenLocal任务,将所有已定义的发布复制到本地Maven缓存,包括其元数据(POM文件等)。

3、配置Gradle插件的信息

主要是配置group 和version就可以了,配置这里的目的是为了将来引入的时候更好的做版本控制

group "com.crazymo.plugin"
version 0.5

4、执行publishPubNamePublishToMavenLocal任务上传到本地仓库

上传成功之后会在本地仓库C:\Users\xxx.m2\repository对应的路径下看到自己的插件库
在这里插入图片描述
经过以上步骤配置之后我们插件的基本信息就配置到了本地仓库。
在这里插入图片描述
如上图所示已经把Java项目默认的产出Jar包上传到了本地仓库,但是如果还需要上传其他额外的信息(比如说源码jar包、doc包或者自定义的POM描述文件等到),还需要在publish节点下增加进行一些配置,最终独立的插件项目 \HiPlugin\build.gradle编译脚本如下:

apply plugin: 'java-library'
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

/************************配置上传到本地仓库 start**************************/
group "com.crazymo.plugin"
version 1.0

//引入maven-publish插件
apply plugin: "maven-publish"
/***************自定义增加额外的产出 start******************/
/**
 * 源代码打包任务(名为hiplugin-1.0-source.jar)
 */
task sourcesJar(type:Jar){
    baseName "hiplugin"
    //分类器,用于区别其他jar包
    classifier "sources"
    //从main源集中的所有代码
    from sourceSets.main.allSource
}

/**
 * 把自动生成的DOc打包成Jar包(名为hiplugin-1.0-doc.jar),
 * 这个docJar 依赖于系统的两个任务,因为只有这两个任务生成doc之后才能把它们的产出打包到一起
 */
task docJar(type: Jar,dependsOn:[javadoc,groovydoc]){
    baseName "hiplugin"
    classifier "doc"
    //从系统javadoc和groovydoc的Doc产出
    from javadoc.destinationDir,groovydoc.destinationDir
}

/**
 * 配置工程工件 即jar产出的配置,默认有java jar包,没有sourcesJar和docJar
 */
artifacts{
    archives sourcesJar
    archives docJar
}
/*****************自定义增加额外的产出 end****************/
publishing{
    publications{
        //其中plugin为任意名称,components.java表示打包编译后的产物即jar包
        plugin(MavenPublication){
            from components.java
            //不指定的话默认为项目名
            artifactId 'hiplugin'
            /*********************把sourcesJar、docJar添加到上传内容****************************/
            artifact sourcesJar
            artifact docJar
            /*********配置pom的内容,pom本身就是以xml数据格式的结构组织的**************/
            pom.withXml() {
                //拿到pom文件的根节点
                def root=asNode()
                //把license节点添加到root节点下的licenses节点
                def licenseNode=root.appendNode("licenses").appendNode('license')
                licenseNode.appendNode("name","Apache License,Version 2.0")
                licenseNode.appendNode("url","https://www.apache.org/license/LICENSE-2.0.txt")
                licenseNode.appendNode("distribution","repo")
                licenseNode.appendNode("comments","A business-friendly OSS license ")
            }
        }
    }
}

/************************配置上传到本地仓库 end**************************/

在这里插入图片描述

进行以下配置之后执行gradlew sourcesJar docJar这两个任务自动生成sourcesJar和docJar:
在这里插入图片描述
经过以上步骤可以在本地源码项目目录中已经生产力两个新的Jar包,但是要想上传到本地仓库,必须配置artifacts节点和publications下的artifact,这样才能把这两个jar包上传到本地仓库,最后在执行publishPubNamePublishToMavenLocal任务上传到本地仓库。
在这里插入图片描述
自动生成的pom文件(内部加载库的时候会去检索pom文件)内容十分简单,如果需要自定义pom的内容(pom结构参考
在这里插入图片描述

5、使用本地仓库中的插件

前面也说了默认情况下Gradle会去自动加载代码仓库中对应路径下的jar包,但是一般默认情况下都是配置了远程代码仓库类似jcenter等,所以要使用本地仓库中的插件还需要在脚本里配置本地仓库的名称和对应的路径,最终app/build.gradle脚本如下:

在这里插入图片描述
应用插件的本质就是引入插件里定义的任务并执行,运行结果如下:在这里插入图片描述

ps:源码传送门,由于篇幅问题,上传插件到JCenter部分知识详见下文。

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/89070618