使用Gradle自定义Android工程插件

前言

在前面几篇对Gradle的使用大致有了认识,我们在实际开发中某些情况下不仅仅需要配置构建而且需要自定义一些构建过程,这个时候就需要用到自定义Gradle插件了,自定义插件有三种方式:

  1. 在build.gradle脚本中直接使用(只在对应的gradle配置文件中使用)。
  2. 在buildSrc中使用(可在工程中随意使用)。
  3. 在独立Module中使用(可以供其他工程引用使用)。

开发Gradle插件可以在IDEA中进行开发,也可以在Android Studio中进行开发,它们唯一的不同,就是IDEA提供了Gradle开发的插件,比较方便创建文件和目录,而Android Studio中,开发者需要手动创建(但实际上,这些目录并不多,也不复杂,完全可以手动创建,而且在AS中进行创建有利于进行插件依赖的调试和功能的完善)。

Build script

在app的build.gradle最底下添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
apply plugin: CustomePlugin

class CustomePlugin implements Plugin<Project>{

@Override
void apply(Project project){
project.task('CustomePluginTask') << {
println "这是一个通过自定义插件方式创建的任务"
}
}
}

可以在:app\Tasks\other\CustomePluginTask看到一个新的task产生,点击执行gradlew CustomePluginTask

1
2
3
4
5
6
7
8
9
10
11
12
D:\tempProject\helloworld>gradlew CustomePlugin
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :app:CustomePlugin
这是一个通过自定义插件方式创建的任务


Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
See https://docs.gradle.org/4.6/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 23s
1 actionable task: 1 executed

这就是一个最简单的自定义Gradle插件了,你可能比较奇怪为什么不使用gradle CustomePluginTask来执行,事实上他们都可以顺利的执行这段task。执行gradlew命令的时候,gradlew会委托gradle命令来做相应的事情,所以gradlew真的只是一个壳而已。

wrapper

gradlew是一个wrapper执行脚本(一般会自动生成到工程根目录),而wrapper是Gradle的一层包装(方便统一Gradle版本和环境)。从gradlew的源码中可得知首先配置了java和其他的运行环境然后执行了%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

上面提到了会自动生成wraper脚本,事实上是因为配置了如下task(这个task我们在Android工程的buildsetup\wrapper也能找到它):

1
2
3
task createWrapper(type: Wrapper) {
gradleVersion = '0.9-preview-1'
}

执行该task: $ gradle createWrapper就会在项目工程生成以下的文件:

1
2
3
4
5
6
7
8
helloworld
│ gradlew
│ gradlew.bat

└─gradle
└─wrapper
gradle-wrapper.jar
gradle-wrapper.properties

当执行gradlew的时候,wrapper会检查当前机器是否已经安装了对应版本的gradle,如果安装了那么gradlew就会委托gradle执行用户输入的命令。如果还未安装的话,那么首先会自动帮我们从gradle repository下载安装。当然你也可以在配置文件中指定想要下载的server来替代默认的gradle repo。当我们从github或者其他地方拿到一个其他人的Android应用的项目代码的时候,假如该项目也是基于Android studio或者gradle开发的,那么我们也可以借助gradlew进行编译从而生成apk。一般来说只需要两条命令就搞定了:

1
2
3
gradlew clean

gradlew build

buildSrc project

新建一个Android Library的Module命名为buildSrc(注意这个名字不可更改),然后修改src\main目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
main
├─groovy
│ └─com
│ └─irain
│ └─hello
│ CustomePlugin.groovy

└─resources
└─META-INF
└─gradle-plugins
com.irain.test.plugin.properties
  • 新建CustomePlugin.groovy如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.irain.hello

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

class CustomePlugin implements Plugin<Project>{

@Override
void apply(Project project){
project.task('CustomePluginTask') << {
println "这是一个通过自定义插件方式创建的任务"
}
}
}
  • 新建com.irain.test.plugin.properties的配置文件如下:
1
implementation-class=com.irain.hello.CustomePlugin
  • 修改build.gradle内容如下:
1
2
3
4
5
6
apply plugin: 'groovy'

dependencies {
implementation gradleApi()
implementation localGroovy()
}

groovy属于gradle核心插件(自带并用短名)的语言支持插件,引入是为了支持groovy语法, 该插件继承自Java插件。

插件ID描述
java向项目添加Java编译,测试和绑定的功能。它作为许多其他Gradle插件的基础。
groovy添加对构建Groovy项目的支持。
scala添加对构建Scala项目的支持。
antlr添加了使用Antlr生成解析器的支持。

我们需要Gradle API接口和类来编译,所以引入gradleApi()插件,我们要使用Gradle所带的Groovy库,就需要声明localGroovy()依赖。

这样我们的buildSrc插件已经创建成功,接下来在app中引入插件来使用(这里的插件路径就是前面提到的配置文件名)。

1
apply plugin: 'com.irain.test.plugin'

Standalone project

上面这种方式也有局限性,就是只能在自己的工程里使用,如果想写一个插件,到处都可以用,那样才是我们的最终目的,这就需要单独建立一个工程,我们在工程里新建一个module目录结构跟上面也基本上没有差别,唯一不同的是build.gradle文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apply plugin: 'groovy'
apply plugin: 'maven'

dependencies {
implementation gradleApi()
implementation localGroovy()
}

group='com.irain.plugin'
version='1.0.0'
archivesBaseName='test-plugin'

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

我们此刻只需要关心一下build.gradle文件的变化,先不解释这些配置的作用,后面再做详细解释,接下来在Gradle面板运行:buildSrc\Tasks\upload\uploadArchives这个task,会发现在工程的根目录会出现一个文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gradlePlugin
└─com
└─irain
└─plugin
└─test-plugin
│ maven-metadata.xml
│ maven-metadata.xml.md5
│ maven-metadata.xml.sha1

└─1.0.0
test-plugin-1.0.0.jar
test-plugin-1.0.0.jar.md5
test-plugin-1.0.0.jar.sha1
test-plugin-1.0.0.pom
test-plugin-1.0.0.pom.md5
test-plugin-1.0.0.pom.sha1

这个就是我们打的maven仓库发布包,在项目根目录的build.gradle中配置仓库地址(目前在本地)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buildscript {

repositories {
maven {
url uri('./gradlePlugin/') //仓库地址
}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.irain.plugin:test-plugin:1.0.0' //依赖路径

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

接下来只需要在app中引入这个插件即可,整个过程就是如此只不过我们省略了上传到maven仓库的过程,接下来我们来通过一个具体的案例看一下如何将插件发布到远程maven仓库。

maven仓库

这里借用Github上面一个发布android apk到fir.im的一个插件项目来说明一下上面我们遇到的疑问,项目地址《msdx/fir-publisher》

1
2
3
4
5
6
7
8
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.githang:fir:0.6.1' // android build plugin 3.0以下版本请使用0.4.3
}
}

说明:jcenter是一个由bintray.com维护的Maven仓库,Maven Central 则是由sonatype.org维护的Maven仓库。

根据如何使用该插件,我们大致可以猜出出来插件在jcenter平台的maven仓库中, 查看地址:https://jcenter.bintray.com/com/githang/fir/0.6.1/。

1
2
3
4
fir-0.6.1-groovydoc.jar
fir-0.6.1-sources.jar
fir-0.6.1.jar
fir-0.6.1.pom

关于如何创建jcenter仓库以及需要的插件可以参考我的另一篇博文《发布Android项目到JCenter仓库》,下面大致分析一下过程。

我们回忆一下前面的博文中提到的上传jcenter的过程,先打maven包然后上传jcenter,需要在build.gradle中添加如下插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
}
}

apply plugin: 'com.jfrog.bintray'

bintray {
user = hasProperty("bintrayUser")?getProperty("bintrayUser"):getProperty("BINTRAY_USER")
key = hasProperty("bintrayKey")?getProperty("bintrayKey"):getProperty("BINTRAY_KEY")

publications = ['mavenJava']
publish = true

pkg {
repo = 'maven'
name = 'TestPlugin'
desc = PROJ_DESCRIPTION
websiteUrl = 'a test gradle plugin project'
issueTrackerUrl = 'https://github.com/lxqxsyu/fir-publisher/issues'
vcsUrl = 'git@github.com:lxqxsyu/fir-publisher.git'
licenses = ['Apache-2.0']
publicDownloadNumbers = true
}
}

上面代码是将本地仓库的pom文件发布到jcenter仓库配置,但是在发布之前我们需要配置maven-publish(该插件继承自publishing插件)借用它来产生本地可发布的pom包,官方参考文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
group='com.irain.plugin'
version='1.0.0'
archivesBaseName='test-plugin'

task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}

task groovydocJar(type: Jar, dependsOn: groovydoc) {
classifier 'groovydoc'
from groovydoc.destinationDir
}

//用于发布文档
artifacts {
archives sourcesJar
archives groovydocJar
}

//定义pom内容
def pomConfig = {
licenses {
license {
name "The Apache Software License, Version 2.0"
url "http://www.apache.org/licenses/LICENSE-2.0.txt"
distribution "repo"
}
}
developers {
developer {
id DEVELOPER_ID
name DEVELOPER_NAME
email DEVELOPER_EMAIL
}
}
}

//maven-publishing
publishing {
publications {
mavenJava(MavenPublication) {
artifactId 'test-plugin'
from components.java
artifact sourcesJar
artifact groovydocJar

pom.withXml {
def root = asNode()
root.appendNode('description', 'a test gradle plugin project')
root.children().last() + pomConfig
}
}
}
}

整个配置在工程中的实际应用请参考项目源码,接下来我们来具体分析一下如何去看懂官方文档并配置。虽然已经可以发布到jcenter了,但是你是不是还是一头雾水,这些知识是从哪里知道的?应该如果修改这些配置呢?

这个就涉及到一个知识体系问题了,你首先得弄懂maven仓库的POM配置,这个文档是很多的,当然也可以稍微参考一下我的博文《Maven构建JavaWeb项目》。POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。执行任务或目标时,Maven 会在当前目录中查找 POM。它读取 POM,获取所需的配置信息,然后执行目标。

POM 中可以指定以下配置:

  • 项目依赖
  • 插件
  • 执行目标
  • 项目构建 profile
  • 项目版本
  • 项目开发者列表
  • 相关邮件列表信息

其次得大致懂得groovy的语法特点和gradle构建的基本过程,这方面可以参考官方教程:https://gradle.org/guides/

最后一点就是需要参考别人的实践来快速入门最后读再读相关官方文档来修改和定制,毕竟时间和精力有限。

幸运的是你看到了这篇博文,你可以在本站项目构建分类中继续阅读其他关于Groovy语法、Gradle标准插件、原理分析等其他文章来完善关于Gradle构建的知识体系,希望能帮助到你。

参考链接

《Gradle自定义插件详解》
《自定义Android Gradle插件》
《Gradle权威指南》

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Banner Buffer Bulma ByteBuffer CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage Element Error Exception Extensions File FileProvider Fresco Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Linux Looper MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Retrofit Runnable RxAndroid RxJava SE0 Spring SpringBoot Statubar Task Theme Thread Tkinter UI UIKit UML VS Code ValueAnimator ViewPropertyAnimator Web Web前端 Workbench api apk by关键字 compileOnly css databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box 七牛 下载 中介者模式 串口 主题 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内核 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 奇点 字符集 存储引擎 宇宙 实践 属性 属性动画 工具 工厂模式 年终总结 异常 弱引用 恒星 打包 技巧 插件 摄影 操作系统 攻略 故事 数据库 数据类型 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 架构 查询 标签选择器 样式 核心 框架 案例 桥接模式 模块化 模板引擎 模板方法模式 泛型 浅色状态栏 源码 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 结构 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 补码 表单 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 锐化 错误 键盘 闭包 降噪 面向对象 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×