阅读 1208

Gradle学习系列(三):Gradle插件

概述

又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记

Gradle系列(一):Groovy学习

Gradle学习系列(二):Gradle核心解密

Gradle学习系列(三):Gradle插件

简介

Gradle本身只是提供了基本的核心功能,其他的特性比如编译Java源码的能力,编译Android工程的能力等等就需要通过插件来实现了。 要想应用插件,需要把插件应用到项目中,应用插件通过 Project.apply() 方法来完成。 在Gradle中一般有两种类型的插件,分别叫做脚本插件对象插件

脚本插件是额外的构建脚本,它会进一步配置构建,可以把它理解为一个普通的build.gradle。

对象插件又叫做二进制插件,是实现了Plugin接口的类,下面分别介绍如何使用它们。

脚本插件

比如我们在项目的根目录创建一个utils.gradle

def getxmlpackage(boolean x){
    def file=new File(project.getProjectDir().getPath()+"/src/main/AndroidManifest.xml");
    def paser = new XmlParser().parse(file)
    return paser.@package
}

ext{
    getpackage = this.&getxmlpackage
}
复制代码

这就是一个简单的脚本插件,然后在app moudle引用这个脚本插件

apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
复制代码

然后就可以调用脚本插件中的方法了

对象插件

对象插件就是实现了org.gradle.api.plugins接口的插件,对象插件可以分为内部插件和第三方插件。

内部插件

Gradle包中有大量的插件,比如我们经常用的java插件和c的插件,我们可以直接引入

apply plugin:'java'
apply plugin:'cpp'
复制代码

三方插件

第三方的对象插件通常是jar文件,要想让构建脚本知道第三方插件的存在,需要使用buildscrip来设置。 在buildscrip中来定义插件所在的原始仓库和插件的依赖 ,再通过apply方法配置就可以了。Android Gradle插件也属于第三方插件,如果我们想引入Android Gradle插件,可以这么写:

buildscript {
    repositories {
    	//配置仓库
        google()
        jcenter()
    }
    dependencies {
    	//配置插件依赖
        classpath 'com.android.tools.build:gradle:3.5.3'
    }
}
//然后就可以在需要的地方引入android 插件了
apply plugin: 'com.android.application'
复制代码

自定义对象插件

自定义一个插件主要是实现 org.gradle.api.Plugin

Build script

这种方式直接在构建脚本中写插件代码,但是这种方式只能在本文件中使用,比如我在app的build.gradle中直接加入如下代码

class myPlugin implements Plugin<Project>{
    @Override
    void apply(Project project) {
        println("myPlugin 执行了")

        project.task("myTask"){
            doLast {
                println("myTask执行了")
            }
        }
    }
}

复制代码

然后引入在本文件定义的插件

apply plugin: myPlugin
复制代码

这个就是直接在build.gradle 中实现了一个插件,在插件中定义了一个MyTask任务,但是这个插件只能在本文件中使用,我们直接执行Mytask任务./gradlew MyTask

> Task :mylibrary2:myTask
myTask执行了
执行阶段,task ':mylibrary2:myTask'耗时:1ms
复制代码

这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本Project,而其他的Project不能使用

buildSrc项目

buildSrc是Gradle默认的插件目录,编译Gradle的时候会自动识别这个目录,将其中的代码编译为插件,

  • 首先先建立一个名为buildSrc的java Module,然后只保留build.gradlesrc/main目录,其他全部删掉,注意名字一定是buildSrc,不然会找不到插件

  • 然后修改Gradle中的内容

apply plugin: 'groovy'  //必须
apply plugin: 'maven'

dependencies {
    implementation gradleApi() //必须
    implementation localGroovy() //必须
    //如果要使用android的API,需要引用这个,实现Transform的时候会用到
    //implementation 'com.android.tools.build:gradle:3.3.0'
}

repositories {
    google()
    jcenter()
    mavenCentral() //必须
}

//把项目入口设置为src/main/groovy
sourceSets {
    main {
        groovy {
            srcDir 'src/main/groovy'
        }
    }
}
复制代码
  • 创建入口目录,在src/main下创建代码入口目录,如下:

  • 然后实现插件代码Text.groovy,注意文件后缀为groovy,文件要引入package com.renxh
package com.renxh

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

class Text implements Plugin<Project>{
    @Override
    void apply(Project project) {
        println("执行自定义插件")
        project.task("haha"){
            doLast{
                println("执行自定义插件 haha task")
            }
        }
    }
}
复制代码
  • 接下来在main目录下创建resources目录,在resources目录下创建META-INF目录,在META-INF目录下创建gradle-plugins目录,在gradle-plugins目录下创建properties文件
  • properties文件可以自己命名,但是要以.properties结尾,比如com.renxh.plugin.properties

  • 最后需要在properties文件中指明我们实现插件的类implementation-class=com.renxh.Text

到目前为止我们的插件项目已经写完了,在module引入我们写的插件apply plugin:'com.renxh.plugin',然后执行插件的Task,./gradlew haha

输出

> Task :mylibrary:haha
执行自定义插件 haha task
执行阶段,task ':mylibrary:haha'耗时:0ms
复制代码

这种形式的写法,在我们整个工程的module都可以使用,但也只是限制在本工程,其他工程不能使用

自定义module,上传maven

第二种写插件的方式他只能在本工程中使用,而其他的项目工程不能使用,有时候我们需要一个插件在多个工程中使用,这时候我们就需要把插件上传maven中

  • 首先先建立一个java module,名字可以任意起,只保留build.gradlesrc/main,其他文件都删除
  • 修改Gradle中内容
apply plugin: 'groovy'  //必须
apply plugin: 'maven'  //要想发布到Maven,此插件必须使用


dependencies {
    implementation gradleApi() //必须
    implementation localGroovy() //必须
}
repositories {
    mavenCentral() //必须
}


def group='com.renxh.cusplugin' //组
def version='2.0.0' //版本
def artifactId='myplugin' //唯一标示


//将插件打包上传到本地maven仓库
uploadArchives {
    repositories {
        mavenDeployer {
            pom.groupId = group
            pom.artifactId = artifactId
            pom.version = version
            //指定本地maven的路径,在项目根目录下
            repository(url: uri('../repos'))
        }
    }
}
复制代码

相比buildSrc方案,这个增加了Maven支持和uploadArchives这样的Task,这个Task作用就是把本地插件打包上传到本地的maven仓库,这里../repos表示项目根目录项下的repos,俩个.回退俩次,回退到项目的根目录

src/main生成groovy文件和生成properties文件的步骤和buildSrc方案一致的

编写插件

class CustomPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {
        project.task("cusplugin"){
            doLast{
                println("cusplugin任务执行111")
            }
        }
    }
}
复制代码

然后执行./gradlew CusPlugin:uploadArchivesuploadArchives任务,然后就可以把repos目录布置到项目根目录了,如图:

repos目录就是本地的Maven仓库,com/renxh/cusplugin就是脚本中指定的groupmyplugin也是脚本中指定的模块名字,是一个唯一标识,1.0.0就是脚本中version

  • 生成本地的maven仓库之后,就需要引用本地maven仓库中的插件了,首先需要在根目录的build.gralde中加入如下:

buildscript {
    repositories {
        google()
        jcenter()
        //引入本地仓库
        maven {
            url uri('./repos') //指定本地maven的路径,在项目根目录下
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        //引入本地仓库中的插件依赖
        classpath 'com.renxh.cusplugin:myplugin:1.0.0'

        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }


}
复制代码
  • classpath指定的路径格式,如下:

这三个参数都是在生成仓库的时候在build.gradle脚本中配置的

classpath '[groupId]:[artifactId]:[version]' 
复制代码
  • 配置完之后就可以在module中使用了
apply plugin: 'com.android.application'
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
apply plugin:'com.renxh.cusplugin'
复制代码
  • 最后就可以执行插件中的任务 ./gradlew app:cusplugin
> Task :app:cusplugin
cusplugin任务执行111
执行阶段,task ':app:cusplugin'耗时:0ms
复制代码

插件的扩展 Extension

我们也可以在插件运行的时候,给插件配置参数,插件的Extension就是用来把参数回传到插件中的

下面我就就给上面的插件添加Extension

首先建一个实体类,来接收参数

package com.renxh.cusplugin
class MyExtension {
    String name;
    int age;
}
复制代码

然后去插件类中添加扩展

class CustomPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {
		//添加扩展
        project.extensions.add('myextension',MyExtension)

        project.task("cusplugin"){
            doLast{
                println("cusplugin任务执行111")

                MyExtension extension = project.myextension
                //3.输出插件扩展属性
                println ">>>>>> name: ${extension.name} age:${extension.age}"
            }
        }
    }
}
复制代码
  • 通过project.extensions.add将自定以的实体类添加到扩展中,并起个名字
  • 然后通过起的名字拿到扩展的实体类
  • 最后拿到扩展中的属性

修改完之后重新上传maven

上传完之后,就可以在module中使用扩展实体类了

apply plugin: 'com.android.application'
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
apply plugin:'com.renxh.cusplugin'

myextension{
    name 'renxh'
    age 27
}
复制代码

最后执行插件中的task,./gradlew app:cusplugin

> Task :app:cusplugin
cusplugin任务执行111
>>>>>> name: renxh age:27
执行阶段,task ':app:cusplugin'耗时:2ms
复制代码

嵌套Extension

我们在android中经常看到这种嵌套的扩展,如下:

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.aliyun.idrs.app"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}
复制代码

defaultConfig就是嵌套在android的里面的,那我们改如何实现这种形式呢?

首先创建一个内部类

class Inner {
    String b

    void b(String b){
        this.b=b
    }
}
复制代码

然后在原来的扩展中加入如下代码

class MyExtension {
    String name;
    int age;

    Inner text = new Inner()

    //创建内部Extension,名称为方法名 text
    void text(Action<Inner> action) {
        action.execute(text)
    }

    //创建内部Extension,名称为方法名 text
    void text(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, text)
    }
}
复制代码

这里面的关键代码就是

    Inner text = new Inner()

  //创建内部Extension,名称为方法名 inner
    void text(Action<Inner> action) {
        action.execute(text)
    }

    //创建内部Extension,名称为方法名 inner
    void text(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, text)
    }
复制代码

这俩个方法是用来创建内部扩展,实际的使用中只需要其中一个方法就行,需要注意的是方法的名字要和成员变量的名字相同

更改plugin中的代码

class CustomPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {

        project.extensions.add('myextension',MyExtension)

        project.task("cusplugin"){
            doLast{
                println("cusplugin任务执行111")

                MyExtension extension = project.myextension
                //3.输出插件扩展属性
                println ">>>>>> name: ${extension.name} age:${extension.age}inner:${extension.text.b}"
            }
        }
    }
}
复制代码

然后就可以在build.gradle中使用了

apply plugin:'com.renxh.cusplugin'

myextension{
    name 'renxh'
    age 27
    text{
        b  "hahah"
    }
}
复制代码

执行插件,输出

> Task :app:cusplugin
cusplugin任务执行111
>>>>>> name: renxh age:27 inner:hahah
复制代码

android中的嵌套Extension

image.png

我们可以点进去看下他们的源码,最终会追溯到BaseExtension类

    private final DefaultConfig defaultConfig;
    private final NamedDomainObjectContainer<BuildType> buildTypes;

    public void defaultConfig(Action<DefaultConfig> action) {
        this.checkWritability();
        action.execute(this.defaultConfig);
    }
    
     public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        this.checkWritability();
        action.execute(this.buildTypes);
    }

复制代码

这个跟我们上面介绍的实现是一样的,如果有时间可以去追溯一下Gradle的源码,但是这个嵌套类里的参数不是必须写的,如下:

myextension{
    name 'renxh'
    age 27
//    text{
//        b  "hahah"
//    }
}
复制代码

也就是说text可以不写

配置不固定数量的Extension

首先创建一个类

class Student{

    Student(String name){
        this.name = name
    }
    String name

    String age

    boolean isMale
}
复制代码

然后在插件中添加

class CustomPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {

        NamedDomainObjectContainer<Student> studentContainer = project.container(Student)
        project.extensions.add('team', studentContainer)

        project.task("cusplugin") {
            doLast {
                println("cusplugin任务执行111")

                NamedDomainObjectContainer<Student> team = project.team

                team.findAll { Student student ->
                    println(student.name + "/" + student.age + "/" + student.isMale)
                }

            }
        }
    }
}
复制代码

这里面最重要的就是project.container()创建用于管理指定类型的命名对象的容器,上面就是创建了Student的容器,然后就可以中build.gradle中调用了

team{
    xiaoming{
        age = 19
        isMale =true
    }

    xiaohong{
        age = 18
        isMale =false
    }
}
复制代码

其中的xiaoming和xiaohong就是name,team这个标签可以定义多个Student元素

输出一下

> Task :app:cusplugin
cusplugin任务执行111
xiaohong/18/false
xiaoming/19/true
执行阶段,task ':app:cusplugin'耗时:1ms
复制代码

参考

Android 自定义Gradle插件的3种方式

Gradle之自定义插件

文章分类
Android
文章标签