AS 自定义 Gradle plugin 插件 案例 [MD]


目录

  • 目录
  • 自定义 Gradle plugin
  • 完整案例
    • 编写插件
      • 创建插件 module
      • 建立 plugin 的目录结构
      • 声明 plugin 信息
      • 配置 build.gradle
      • 编写插件逻辑
      • 生成 plugin 插件
    • 传递参数
      • 定义参数
      • DefaultTask
      • 建立映射关系
      • 定义外部参数
    • 发布插件到仓库
    • 使用插件
      • 引入 cooker-plugin
      • 编译
      • 运行 task
  • 一些细节
    • 发布到本地仓库
    • 使用插件
    • 发布到Jcenter仓库
    • 最佳实践

我的GitHub 我的博客 我的微信 我的邮箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

自定义 Gradle plugin

  • 参考:AndroidStudio中自定义 Gradle plugin
  • 参考:Gradle 插件开发
  • 官方文档:Writing Custom Plugins

结合 AndroidStudio,自定义Gradle plugin可以完成很多功能,比如:添加编译依赖、进行Aspecj编译、自动生成混淆配置。

项目中引入自定义 Gradle plugin 一般有三种方法:

  • 直接写在 build.gradle 中,这种方式的缺点是无法复用插件代码,在其他项目中还得复制一遍代码
  • plugin 源码放到 rootProjectDir/buildSrc/src/main/groovy 目录下(没用过)
  • plugin 打包后发布到 maven 仓库, 然后项目通过依赖的形式引入

下面介绍的是第 3 种方式

完整案例

编写插件

创建插件 module

新建一个Android工程,在这个工程里面新建一个Android Library,先起名叫 cooker-plugin 吧,我们将会用这个 library 写 Gradle plugin

建立 plugin 的目录结构

把这个 cooker-plugin 中除了build.gradle文件外的默认文件都删除,然后按照下面新建文件:

  • 在新建的module中新建文件夹src,接着在src文件目录下新建main文件夹,在main目录下新建groovy目录,这时候groovy文件夹会被Android识别为groovy源码目录。
  • 除了在main目录下新建groovy目录外,你还要在main目录下新建resources目录,同理resources目录会被自动识别为资源文件夹。
  • 在groovy目录下新建项目包名,就像Java包名那样。
  • 在resources目录下新建文件夹META-INF,META-INF文件夹下新建gradle-plugins文件夹。

这样,就完成了gradle 插件的项目的整体搭建,之后就是小细节了。目前,项目的结构是这样的:

声明 plugin 信息

src/main/resources/META-INF/gradle-plugins 里声明 plugin 信息,比如新建cooker-plugin.properties文件(文件名 cooker-plugin 是插件名称),在其中指定插件的实现类的全路径类名:

implementation-class=com.helen.plugin.CookerPlugin

配置 build.gradle

在 build.gradle 中声明用 groovy 开发

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

repositories {
    mavenCentral()
}

编写插件逻辑

插件代码放在 src/main/groovy 下,实现 plugin,其实就是实现 Plugin 接口

package com.helen.plugin

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

class CookerPlugin implements Plugin {
    @Override
    void apply(Project project) {
        println "这里实现plugin的逻辑!"
        project.task('cooker-test-task').doLast { variant ->
            println "自定义任务-doLast"
        }.doFirst { variant ->
            println "自定义任务-doFirst"
        }
    }
}

生成 plugin 插件

在 cooker-plugin 项目中,build 一下

任务完成以后,就能在build/libs下生成对应的 plugin 插件了

现在这个插件就能使用了,可以发布在本地仓库或者 Maven 仓库

传递参数

接下来我们介绍如何获得自定义的参数

定义参数

新建 PluginExtension.groovy,用于定义我们可以支持的参数:

class PluginExtension {
    def param1 = "param1 defaut"
    def param2 = "param2 defaut"
    def param3 = "param3 defaut"
}

我们希望能传入嵌套的参数,所以再新建一个 PluginNestExtension.groovy:

class PluginNestExtension {
    def nestParam1 = "nestParam1 defaut"
    def nestParam2 = "nestParam2 defaut"
    def nestParam3 = "nestParam3 defaut"
}

DefaultTask

新建一个 CustomTask.groovy,继承 DefaultTask 类,使用 @TaskAction 注解标注实现的方法:

class CustomTask extends DefaultTask {

    @TaskAction
    void output() {
        println "param1 is ${project.pluginExt.param1}"
        println "param2 is ${project.pluginExt.param2}"
        println "param3 is ${project.pluginExt.param3}"
        println "nestparam1 is ${project.pluginExt.nestExt.nestParam1}"
        println "nestparam2 is ${project.pluginExt.nestExt.nestParam2}"
        println "nestparam3 is ${project.pluginExt.nestExt.nestParam3}"
    }
}

这里我们只是做了拿到了参数,然后做最简单的输出操作,使用 ${project.pluginExt.param1}${project.pluginExt.nestExt.nestParam1} 等拿到使用者设置的值。

建立映射关系

在 apply 方法中建立映射关系:

project.extensions.create('pluginExt', PluginExtension)
project.pluginExt.extensions.create('nestExt', PluginNestExtension)
project.task('customTask', type: CustomTask)

定义外部参数

这里我们定义了param1,param2,nestParam1,nestParam2,而param3和nestParam3保持默认。

pluginExt {
    param1 = 'app param1'
    param2 = 'app param2'
    nestExt {
        nestParam1 = 'app nestParam1'
        nestParam2 = 'app nestParam2'
    }
}

这样之后,在执行customTask时就会输出使用者对自定义的参数设置的值

发布插件到仓库

发布到仓库的方式有很多,下面只介绍利用 mavenDeployer 插件发布在本地仓库

1、引入 mavenDeplayer 插件

修改 cooker-plugin 的 build.gradle,添加如下内容:

apply plugin: 'maven'//添加maven plugin,用于发布我们的jar
uploadArchives {
    repositories {
        mavenDeployer {
            pom.groupId = 'com.helen.plugin'
            pom.artifactId = 'cooker-plugin'
            pom.version = 1.0
            repository(url: uri('../release'))   //文件发布目录(相对当前 build.gradle 的路径)
        }
    }
}

2、用 uploadArchices 发布

运行 uploadArchives 就能在设置的仓库路径中生成 cooker-plugin 了

使用插件

引入 cooker-plugin

在 build.gradle 中引入 cooker-plugin

buildscript {
    repositories {
        jcenter()
        maven {
            url uri('release') //cooker-plugin 所在的仓库,这里是本地目录(相对当前 build.gradle 的路径)
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'com.helen.plugin:cooker-plugin:1.0'  //引入cooker-plugin
    }
}
apply plugin: 'cooker-plugin'

编译

我们编译 App 的时候 cooker-plugin 就会介入了

每次 clean/build 时, 在 Gradle Console 可以看到我们的 log

这里实现plugin的逻辑!

运行 task

前面demo中,我们新建了两个task: cooker-test-taskcustomTask, 我们可以通过两种方式运行这两个task,一种方式是双击 app > other > task 名,一种是通过 gradlew 命令

cooker-test-task 打印的日志:

Executing tasks: [cooker-test-task]

这里实现plugin的逻辑!
:app:cooker-test-task
自定义任务-doFirst
自定义任务-doLast

customTask 打印的日志:

Executing tasks: [customTask]

这里实现plugin的逻辑!
:app:customTask
param1 is app param1
param2 is app param2
param3 is param3 defaut
nestparam1 is app nestParam1
nestparam2 is app nestParam2
nestparam3 is nestParam3 defaut

到此为止, 自定义Gradle plugin的基本过程就介绍完了。

一些细节

参考:如何使用Android Studio开发Gradle插件

前段时间,插件化以及热修复的技术很热,Nuwa热修复的工具NuwaGradle,携程动态加载技术DynamicAPK,还有希望做最轻巧的插件化框架的Small。这三个App有一个共同的地方就是大量的使用了Gradle这个强大的构建工具,除了携程的框架外,另外两个都发布了独立的Gradle插件提供自动化构建插件,或者生成热修复的补丁。所以学习一下Gradle插件的编写还是一件十分有意义的事。

Gradle插件是使用Groovy进行开发的,而Groovy其实是可以兼容Java的。Android Studio其实除了开发Android App外,完全可以胜任开发Gradle插件这一工作。

这样就完成了最简单的一个gradle插件,里面有一个叫testTask的Task,执行该task后会输出一段文字,就像当初我们输出HelloWorld一样。

发布到本地仓库

接着,我们需要将插件发布到maven中央仓库,我们将插件发布到本地仓库就好了,在module项目下的buidl.gradle文件中加入发布的代码。

repositories {
    mavenCentral()
}
group='cn.edu.zafu.gradle.plugin'
version='1.0.0'

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('../repo'))
        }
    }
}

maven本地仓库的目录就是当前项目目录下的repo目录。这时候,右侧的gradle Toolbar就会在module下多出一个名为uploadArchives的task,点击这个Task,就会在项目下多出一个repo目录,里面存着这个gradle插件。

使用插件

发布到本地maven仓库后,我们就使用它。在叫app的android项目下的gradle.build的文件中加入

buildscript {
    repositories {
        maven {
            url uri('../repo')
        }
    }
    dependencies {
        classpath 'cn.edu.zafu.gradle.plugin:time:1.0.0'
    }
}
apply plugin: 'plugin.test'

同步一下gradle,右侧app下other分类下就会多出一个testTask,双击执行这个Task,控制台就会输出刚才我们输入的字符串。

发布到Jcenter仓库

配置完以后,右侧的gradle的toolbar就会多出几个task。

之后我们先运行other下的install这个task,再执行bintrayUpload这个task,如果不出意外,就上传了,之后不要忘记到后台add to jcenter。成功add到jcenter之后就会有link to jcenter的字样。

耐心等待add to center成功的消息,之后就可以直接引用了。

最佳实践

最佳实践的来源是源自multidex,为什么呢,因为最近当方法数超了之后,如果选择multidex,编译的过程就会慢很多很多,为了检测到底是哪一步的耗时,需要编写一个插件来统计各个task执行的时间,因此就有了这么一个最佳实践。

在PluginImpl同级目录下新建TimeListener.groovy文件:

class TimeListener implements TaskExecutionListener, BuildListener {
    //...
}

然后将PluginImpl文件中的apply方法修改为

void apply(Project project) {
   project.gradle.addListener(new TimeListener())
}

完成后打包发布到jcenter(),之后你只要引用了该插件,就会统计各个task执行的时间。

2020-04-08