自定义Gradle插件

Gradle插件让我们把可以复用的构建逻辑打包起来,这样就可以用到其他项目当中。我们可以在Gradle中引用自己的自定义插件。下面介绍几种自定义插件的方法:

  • Build script
  • buildSrc project
  • Standalone project

Build script

这种方式直接把插件的代码写入build script当中,好处是除了编写代码之外不用配置其他的引入参数,坏处就是只能在此build script当中使用这个插件,无法复用。在创建自定义插件时需要编写一个插件的实现。 Gradle实例化插件并使用Plugin.apply()方法调用插件实例。当我们引用插件时:“apply plugin: 'id'”,apply()方法就会被执行。

新建一个自定义插件

apply plugin: 'com.android.application'
apply plugin: MyCustomGradle

class MyCustomGradle implements Plugin<Project>{

    @Override
    void apply(Project project) {
        project.task("customplugin"){
            doLast {
                println "This is my custom plugin!"
            }
        }
    }
}

android {
     ......
}

运行这个插件定义的Task:

D:\Android\Workspace\CustomGradlePlugin>gradlew "customplugin"
NDK is missing a "platforms" directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory.  It is currently set to D:\Android\sdk\ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.

Incremental java compilation is an incubating feature.
:app:customplugin
This is my custom plugin!

BUILD SUCCESSFUL

Total time: 2.29 secs

值得注意的是Gradle会为每个引用这个插件的project创建一个这个plugin 的实例。在apply中给Project添加了一个新属性,然后在script中配置这个属性的值。配置的时候这个配置块的名字要和新属性的名字对应。这里要注意apply的时候并没有执行到script,所以如果在apply中获取配置的值是获取不到的,需要在task里面添加doLast或doFirst,等到执行task的时候再去获取值。

从脚本中获取输入

大多数的插件都需要从build script当中获取配置信息。Project通过ExtensionContainer来追踪那些传给插件的配置信息。为了能够从脚本中获取信息,我们要在extension container中添加一个Java Bean:

project.extensions.create("myplugin", MyPluginExtension)

MyPluginExtension:

class MyPluginExtension{
    String message;
}

这样我们就在Project中新建了一个名为“myPlugin”且类型为“MyPluginExtension”的属性,可以直接通过project.myPlugin访问。而且在分析到myPlugin{}这个script block时就会把{}这个闭包的代理设置为MyPluginExtension,在MyPluginExtension的上下文中运行这个闭包。

myplugin{
    message "Thank you!";
}

就相当于:

myPluginExtension.setMessage("Thank you!")

因为Groovy会自动加上setter和getter,所以我们在定义MyPluginExtension时不必再写。之后再插件中获取配置的值的方式则为:

project.myPlugin.message

整个代码片段:

apply plugin: 'com.android.application'
apply plugin: MyCustomGradle

class MyCustomGradle implements Plugin<Project>{

    @Override
    void apply(Project project) {

        project.extensions.create("myplugin", MyPluginExtension)

        project.task("customplugin"){
            doLast {
                println "This is my custom plugin!" + project.myplugin.message;
            }
        }
    }
}

class MyPluginExtension{
    String message;
}

myplugin{
    message "Thank you!";
}

执行Task:

D:\Android\Workspace\CustomGradlePlugin>gradlew "customplugin"
NDK is missing a "platforms" directory.ompiling 
......

:app:customplugin
This is my custom plugin!Thank you!

BUILD SUCCESSFUL

Total time: 2.802 secs

buildSrc project

单独写Gradle Plugin文件,放在rootProjectDir/buildSrc/src/main/groovy/目录下,同一个工程中所有的构建文件都可以引用这个插件,但是不能被其他工程引用。

新建一个名为buildSrc的工程,再构建如下目录,这里的buildSrc是多工程项目:

buildSrc/build.gradle:

buildscript {
    repositories {
        mavenCentral()
    }
}

my-gradle-plugin/build.gradle:

apply plugin:'groovy'

repositories {
    mavenCentral()
}

rootProject.dependencies{
    runtime project(path)
}

dependencies {
    compile localGroovy()
    compile gradleApi()
}

因为这里buildSrc底下有多个工程,所以要配置rootProject.dependencies{},同时还要设置仓库,不然编译的时候会找不到插件。官方文档的说法是:

The buildSrc project can be a multi-project build, just like any other regular multi-project build. However, all of the projects that should be on the classpath of the actual build must be runtime dependencies of the root project in buildSrc.

要在每个插件子工程的build.gradle中都配置runtime依赖。

接着settings.gadle,如果是多项目这个文件一定不能忘记:

include "plugins/my-gradle-plugin"

myplugin.properties:

implementation-class=com.example.plugin.AppPlugin

这个文件的目的主要是设置插件的id,告诉Gradle这个插件的入口类在哪里。像上面那样配置之后我们就可以这样引入这个插件:

apply plugin: 'myplugin'

如果没有通过properties文件指定id,这样引入插件也行:

apply plugin: com.example.plugin.AppPlugin.class

之后在同工程的app中引用这个插件:

apply plugin: 'com.android.application'
apply plugin: 'myplugin'

myplugin{
    message "buildSrc!"
}

运行Task:

D:\Android\Workspace\MyBuildSrc>gradlew -q customplugin
This is my custom plugin!buildSrc!

采用buildSrc project这种方式来整合插件虽然看起来简单,不需要再配置classpath直接就能在其他子工程中引用,但是具体实现起来很麻烦,编译的时候常常提示找不到自定义的插件。有时候可能是因为在buildSrc在编译并添加到classpath之前子项目的build script就已经被编译且缓存了,这时候可以利用“gradlew --recompile-scripts”来强制重新编译。

Standalone project

这种方法把我们的插件代码移到一个独立的项目当中,这样我们就可以发布或者和别人分享我们的插件。这个项目简单来说就是构建一个JAR包,这个JAR包包含了我们在插件中所写的类。

构建JAR包

首先新建一个Android Library的module,名为“myplugin“。把myplugin的build.gradle文件的内容改为:

apply plugin: 'groovy'
apply plugin: 'maven'

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

uploadArchives {
    repositories{
        mavenDeployer {
            repository(url: uri(LOCAL_REPO_URL))
            pom.groupId = PROJ_GROUP
            pom.artifactId = PROJ_ARTIFACTID
            pom.version = PROJ_VERSION
        }
    }
}

uploadArchives就是打包成Task的配置。其中的配置常量可以写在gradle.properties文件中。设置了pom.groupId、pom.artifactId和pom.version之后,如果我们要引入这个插件,只要在buildscript{}的dependencies{}当中这样设置classpath即可:

classpath 'pom.groupId : pom.artificatId : pom.version'

在myplugin的根目录下新建文件gradle.properties:

PROJ_NAME=myplugin

PROJ_POM_NAME=Local Repository

LOCAL_REPO_URL=D:/repos

PROJ_GROUP=com.example.myplugin
PROJ_ARTIFACTID=myplugin
PROJ_VERSION=1.0.0
PROJ_VERSION_CODE=1

//项目描述
PROJ_WEBSITEURL=http://kvh.io
PROJ_ISSUETRACKERURL=https://github.com/kevinho/Embrace-Android-Studio-Demo/issues
PROJ_VCSURL=https://github.com/kevinho/Embrace-Android-Studio-Demo.git
PROJ_DESCRIPTION=demo apps for embracing android studio

PROJ_LICENCE_NAME=The Apache Software License, Version 2.0
PROJ_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
PROJ_LICENCE_DEST=repo

DEVELOPER_ID=your-dev-id
DEVELOPER_NAME=your-dev-name
DEVELOPER_EMAIL=your-email@your-mailbox.com

这个文件主要配置一些常量。

在main文件夹下面新建一个groovy文件夹和一个resources文件夹:

在groovy文件夹下面新建com.example.myplugin的包,在包下面分别新建MyCustomGradle.groovy文件和MyPluginExtension.groovy文件。接着在resources文件夹下面新建名为META-INF的文件夹,再在META-INF下面新建gradle-plugins文件夹,在gradle-plugins文件夹下面新建com.example.myplugin.properties文件。在这个文件中写好入口类:

//入口
implementation-class = com.example.myplugin.MyCustomGradle

这里com.example.myplugin.properties的命名需要注意,比如我命名为com.example.myplugin.properties,那我后面要在app中引入这个插件时就是这么写的:

apply plugin: com.example.myplugin

这样我的目录结构就成了这样:

Android Studio中会把文件的后缀名隐藏起来,会导致编译出错或找不到自定义的插件,所以要手动补上去。尤其是properties文件和groovy文件。

打开Android Studio右侧的Gradle,找到构建JAR包的Task,运行一下,就生成了我们想要的JAR包:

打开我们指定的生成路径:

在1.0.0文件夹中可以看到一个pom文件,里面就记录着我们在前面配置的信息:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.myplugin</groupId>
  <artifactId>myplugin</artifactId>
  <version>1.0.0</version>
</project>

在项目中使用自定义插件

首先要在根项目的build.gradle文件中设置好仓库和依赖:

buildscript {
    repositories {
        maven{
            url uri(LOCAL_REPO_URL)
        }
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'com.example.myplugin:myplugin:1.0.0'
    }
}

然后在app module的build.gadle文件中引入插件,并配置好信息:

apply plugin: 'com.android.application'
apply plugin: 'com.example.myplugin'

myplugin{
    message "Standlone Project!";
}
......

然后执行这个Task:

D:\Android\Workspace\CustomGradlePlugin>gradlew -p app customplugin
NDK is missing a "platforms" directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory.  It is currently set to D:\Android\sdk\ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.

Incremental java compilation is an incubating feature.
:app:customplugin
This is my custom plugin!Standlone Project!

BUILD SUCCESSFUL

Total time: 2.758 secs
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容