使用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权威指南》