Android Gradle Plugins系列-01-自定义Gradle插件入门指南

前言

本文内容已经有很多大佬写过了,不过这里为了知识体系的完整,就再写一遍,并加入Maven Publish插件的使用,不感兴趣跳过就好。官方文档:Developing Custom Gradle Plugins在这。

插件的开发语言

官方说了,不管你用啥语言,只要最终能编译成JVM 字节码就行。不过一般使用Java,Kotlin,Groovy的居多,而静态语言类型的Java和Kotlin相对于Groovy性能更好。

插件打包的三种方式

Build Script

直接build script中编写插件的源代码。这样做的好处是插件会自动编译并包含在build script的classpath中,不用我们执行任何操作。但是,插件在build script之外是不可见的,所以不能在定义它的build script之外复用这个插件。

buildSrc project

将插件的源代码放在 rootProjectDir/buildSrc/src/main/java 目录(或 rootProjectDir/buildSrc/src/main/groovy 或 rootProjectDir/buildSrc/src/main/kotlin,具体取决于使用哪种语言)。 Gradle 负责编译和测试插件,并使插件在build script的classpath上可用。该插件对项目里的所有build script都是可见的。 但是对其它项目就不可见了,因此其它项目不能复用该插件。

有关 buildSrc 项目的更多详细信息,请阅读组织 Gradle 项目

Standardalone project

为插件创建一个单独的项目(或单独的Module),最终编译并发布一个 JAR。通常来说,这个 JAR 可能包含一些插件,或者将几个相关的任务类捆绑到一个库中,或者两者的某种组合。

一般我们开发自定义插件,都是选择Standardalone project的方式居多,编译打包成JAR后,发布到Maven仓库(本地,远程,私有或公有都可以),以便提供给其它项目使用,这样的好处是方便复用,项目编译速度也会快一些。

编写一个简单的插件

创建 Gradle 插件,需要编写一个实现 Plugin 接口的类。 当插件应用于项目时,Gradle会自动创建插件类的实例并调用该实例的 Plugin.apply() 方法。 项目对象作为参数传递,插件可以使用它来配置项目。

在Build Script编写插件

下面的示例包含一个问候插件(GreetingPlugin类),它向项目添加了一个 hello 任务,用于输出一句问候的话。在CustomGradlePlugin Module的build.gradle脚本(groovy语言)中添加以下代码:

plugins {
    id 'maven-publish'
    id 'java-library'
    id 'kotlin'
}

//---------------------------------自定义插件----------------------------------
//添加一个名为GreetingPlugin的插件
class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin
//---------------------------------自定义插件----------------------------------


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "com.android.tools.build:gradle:3.5.0"
    implementation gradleApi()
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30"
    implementation group: 'org.ow2.asm', name: 'asm', version: '7.2'
    implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.2'
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.nxg.plugin'
            artifactId = 'custom-plugin'
            version = '1.0.0'
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

点击Sync Projects With Gradle File,就会生成一个名为hello的task,可以在右上角的Gradle页面查看。

通过Gradle Task 执行插件

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

双击或右键执行,即可在控制台看到对应的打印。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

至此,一个简单的插件就完成了。

通过命令行执行插件

你可能想使用命令行执行插件,在Terminal中执行:sh gradlew -q hello./gradlew -q hello

Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.

你可能会遇到报错如下:

nxg:/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample$ sh gradlew -q hello

FAILURE: Build failed with an exception.

* Where:
Build file '/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample/app/build.gradle' line: 2

* What went wrong:
An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.application'.
   > Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
     You can try some of the following options:
       - changing the IDE settings.
       - changing the JAVA_HOME environment variable.
       - changing `org.gradle.java.home` in `gradle.properties`.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s
nxg:/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample$ 

解决办法是进入设置界面修改Gradle JDK的版本为JDK 11。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

但是笔者测试发现这样还不能解决问题,需要在项目根目录的gradle.properties文件中指定JAVA_HOME的路径。

//JDK路径可以在Setting--->Build,Exceution,Deployment--->Build Tools--->Gradle JDK中获取
org.gradle.java.home=/work/android/android-studio-4.0/android-studio/jre

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

在buildSrc目录编写插件

新建buildSrc目录

在项目的根目录新建一个名为buildSrc的目录,注意名称一定要对。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

新建build.gradle

由于新建的buildSrc什么都没有,显然是不能正常工作,此时需要手动建一个build.gralde文件,并填入以下代码:

plugins {
    //使用maven-publish插件
    id 'maven-publish'
    //使用java库,用于开发插件
    id 'java-library'
}

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

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.nxg.plugin'
            artifactId = 'custom-plugin'
            version = '1.0.0'
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
//指定源码/资源路径
sourceSets {
    main {
        java {
            srcDir 'src/main/java'
            //srcDir 'src/main/kotlin'
        }
        groovy {
            srcDir 'src/main/groovy'
        }
        resources {
            srcDir 'src/main/resources'
        }
    }
}

Sync Project With Gradle Files,buildSrc目录下就会生成.gralde和build目录。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

新建源码目录

根据此路径rootProjectDir/buildSrc/src/main/java 目录(或 rootProjectDir/buildSrc/src/main/groovy 或 rootProjectDir/buildSrc/src/main/kotlin,具体是哪个路径要看你用的开发语言)新建对应的目录,在对应目录中新建自己的包。

右键buildSrc目录,New--->Directory,新建目录,由于build.gradle中已经配置了sourceSets,因此这里直接就提示出来了,双击对应的路径既可完成新建。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

这里为了演示,Java,Kotlin,Groovy的目录都创建了,实际插件还是用的Java。

新建插件类

在对应开发语言的目录,创建插件的包名,再新建一个GreetingPlugin类实现Plugin<Project>接口,代码如下:

package com.nxg.plugins;

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

public class JavaGreetingPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        System.out.println("JavaGreetingPlugin(buildSrc) ---> apply");
        project.task("hello").doLast(task -> System.out.println("Hello from the com.nxg.plugins.JavaGreetingPlugin(buildSrc)"));
    }
}

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

新建properties文件

同样右键buildSrc目录,New--->Directory,新建目录resources,然后按照以下路径:

resources/META-INF/gradle-plugins/com.nxg.plugins.greeting.properties

建立对应的目录和properties文件。

xxx.properties文件,这里的xxx对应的插件id,com.nxg.plugins.greeting.properties的id就是com.nxg.plugins.greeting,通常用包名+插件名命名,或者用其他唯一标示都是可以的。id通常是在bulid.gradle中使用plugins依赖插件时用于指定插件。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
}

com.nxg.plugins.properties的内容为:

implementation-class=com.nxg.plugins.JavaGreetingPlugin

implementation-class=xxx,xxx填写插件的包名加类名。

官方原文说明如下:

Behind the scenes

So how does Gradle find the Plugin implementation? The answer is - you need to provide a properties file in the JAR’s META-INF/gradle-plugins directory that matches the id of your plugin, which is handled by Java Gradle Plugin Development Plugin.

Example: Wiring for a custom plugin

src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties

implementation-class=org.example.GreetingPlugin

Notice that the properties filename matches the plugin id and is placed in the resources folder, and that the implementation-class property identifies the Plugin implementation class.

遇到的问题

Gradle sync failed: Entry META-INF/gradle-plugins/com.nxg.plugins.properties is a duplicate but no duplicate handling strategy has been set. Please refer to https://docs.gradle.org/7.0.2/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy for details. (650 ms)

看打印,先入为主的认为是properties文件重复导致了,找了一圈github,官方论坛,*,尝试各种配置duplicatesStrategy,均无效。最后使用另外的项目来新建buildSrc,发现竟然成功了,对比后发现,是少了buildSrc里的build.gradle文件,再深入对比后发现是build.gradle里的maven-publish插件的问题,将插注释掉重新sync就好了(或者删除整个build.gradle也行,测试发现是可以的,不影响),buildSrc也用不到maven-publish。

使用插件

在app的build.gradle,使用以下方式依赖写好的自定义插件,其中id对应的值com.nxg.plugins.greeting,即是com.nxg.plugins.greeting.properties的文件名。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
}

// Apply the plugin,这两种方式都是支持的
//apply plugin: 'com.nxg.plugins.greeting'

插件的使用更详细的内容参考官方文档Using Gradle Plugins

运行插件

添加好自定义插件的依赖后,点击Sync Projects With Gradle File,就会生成一个名为hello的task,可以在右上角的Gradle页面查看,同时Build控制台中也打印出apply方法的日志。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

双击执行hello task,即可看到相关日志打印。

独立的自定义插件项目

Build Script和buildSrc的方式写插件虽然方便编译和调试,但却不能共享给多个项目使用。对于多项目开发的使用场景,将自定义的插件移动到一个独立的项目中,以便可以发布它并分享出去是一个更好的选择。

自定义插件项目的编译产物(Plugin Marker Artifact)通常是一个包含插件类的 JAR,而打包和发布插件的最简单且推荐的方法是使用Java Gradle Plugin Development Plugin。这个插件将自动使用Java Plugin,并且自动将gradleApi()依赖项添加到 api 配置中,且最终生成的 JAR 文件中会包含所需的插件描述符(com.nxg.plugins.greeting.properties),并配置发布时要使用的Plugin Marker Artifact

新建Android Library Module

ModuleName和Package 根据需要修改,这里笔者选择Kotlin语言,Bytecode Level选择默认8。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

删掉不必要的包和文件,最终的目录如下:

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

使用java-gradle-plugin插件

删除build.gradle里的内容,添加java-gradle-plugin插件,并配置插件id和实现类。

plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        standaloneGradlePlugins {
            id = "greeting-standalone"
            implementationClass = 'com.nxg.plugins.GreetingStandaloneGradlePlugins'
        }
    }
}

更多介绍,点击阅读java-gradle-plugin的使用。

新建插件类

根据以上插件配置,新建对应的插件类:

package com.nxg.plugins;

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

public class GreetingStandaloneGradlePlugins implements Plugin<Project> {

    private static final String TAG = "GreetingStandaloneGradlePlugins";

    @Override
    public void apply(Project project) {
        System.out.println("GreetingStandaloneGradlePlugins(standalone) ---> apply");
        project.task("helloStandalone").doLast(task -> System.out.println("Hello from the com.nxg.plugins.GreetingStandaloneGradlePlugins(standalone)"));
    }
}

一个简单的插件就完成了。

配置MavenPublish插件

使用maven-publish插件并配置如下:

plugins {
    id 'java-gradle-plugin'
    id 'maven-publish'
}

//定义Maven仓库信息
def MAVEN_GROUP_ID = "com.nxg.plugins"
def MAVEN_ARTIFACT_ID = "greeting-standalone"
def MAVEN_VERSION = "1.0.0"
def MAVEN_NAME = "repo"
def MAVEN_RELEASE_URL = "mavenLocal"
def MAVEN_USERNAME = ""
def MAVEN_PASSWORD = ""


gradlePlugin {
    plugins {
        standaloneGradlePlugins {
            id = MAVEN_ARTIFACT_ID
            implementationClass = 'com.nxg.plugins.GreetingStandaloneGradlePlugins'
        }
    }
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = MAVEN_GROUP_ID
            artifactId = MAVEN_ARTIFACT_ID
            version = MAVEN_VERSION
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir(MAVEN_NAME)
        }
    }
}

maven-publish插件入门和踩坑记录见:

Android Gradle Plugins系列-02-Maven Publish 插件入门指南

发布插件到maveLocal

配置好后,点击Sync Projects With Gradle File,即可在右上角的gradl task list中看到对应module的publish tasks。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

双击执行publistoMavenLocal,即可发布插件的Artifact(构建产物)到本地Maven仓库,一般是在.m2(mavenLocal)目录中。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

发布插件到指定目录

我想发布到指定目录行不行?当然可以。其实上面已经配置好路径了,url可以替换为自定义路径,笔者这里配置的是build目录的repo文件夹。

publishing {

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir(MAVEN_NAME)
        }
    }
}

双击执行publish task,执行完毕后会在url配置的路径中生成插件的构建产物。

Android Gradle Plugins系列-01-自定义Gradle插件入门指南

使用插件

插件的使用大同小异,不过要注意的是,发布的到mavenLocal的插件不能通过Gradle DSL(plugins{})或者apply直接使用,原因是插件的Artifact(构建产物)不在classpath中。

通过apply的方式使用插件需要在buildscript中指定本地maven仓库路径,并指定classpath,并且buildscript代码块要在plugins代码块之前声明,具体如下:

buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath 'com.nxg.plugins:greeting-standalone:1.0.0'
    }
}

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
    //id 'com.nxg.plugins.greeting-standalone' version '1.0.0'
}

// Apply the plugin
apply plugin: 'greeting-standalone'

那我想通过plugins 指定id行不行?当然那行,将插件发布到JFrog Bintray即可。

那我不想发布,就想使用mavenLocal的插件呢?理论上应该可以,但是笔者按照官方文档测试,怎样修改的不行。

具体做法是在项目根目录中的settings.gradle中添加pluginManagement如下:

dependencyResolutionManagement {
    //repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        pluginManagement {
            repositories {
                mavenLocal()
                gradlePluginPortal()
            }
        }
        google()
        mavenCentral()
        mavenLocal()
    }
}

rootProject.name = "AndroidGradlePluginsSample"
include ':app'
include ':GradlePluginInBuildScript'
include ':StandaloneGradlePluginProject'

并且带上版本号:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
    id 'com.nxg.plugins' version '1.0.0'
}

还是报错如下:

Build file '/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample/app/build.gradle' line: 16

Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'com.nxg.plugins.greeting-standalone:com.nxg.plugins.greeting-standalone.gradle.plugin:1.0.0')
  Searched in the following repositories:
    MavenLocal(file:/home/lb/.m2/repository/)

真的难搞哦。看下面的日志,很明显压根对不上啊。

Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:
跟这个
Plugin Repositories (could not resolve plugin artifact 'com.nxg.plugins.greeting-standalone:com.nxg.plugins.greeting-standalone.gradle.plugin:1.0.0')
 再看看classpath
  classpath 'com.nxg.plugins:greeting-standalone:1.0.0'

此问题留着以后有时间再看看吧。

插件的使用更详细的内容还是建议参考官方文档Using Gradle Plugins,多看看官方文档,顺便学学英语。

参考资料

Developing Custom Gradle Plugins

上一篇:怎么实现将word中的公式导入(或粘贴)到编辑中ueditor


下一篇:2021年最新IDEA激活码,IDEA永久激活码,IDEA激活