Android 自定义Gradle插件的3种方式

前言

Gradle插件在Android中的应用很广泛,很多字节码插桩方案就用到了这方面的知识,Android官方提供了很多可用的插件,比如apply plugin: 'com.android.application':它表示生成一个apk应用的插件;apply plugin: 'com.android.library':它表示生成AAR包。

本文只是为入门Gradle插件提供一些思路与实践方案,不深入解析Gradle工作原理和Task相关的内容。

Gradle插件官方文档地址:
https://docs.gradle.org/current/userguide/custom_plugins.html

Gradle Plugins简介

Gradle插件打包了可重用的构建逻辑,可以在不同的项目中使用。Gradle提供了几种方式来让你实现自定义插件,这样你可以重用你的构建逻辑,甚至提供给他人使用。

可以使用多种语言来实现Gradle插件,其实只要最终被编译为JVM字节码的都可以,常用的有GroovyJavaKotlin。通常,使用Java或Kotlin(静态类型)实现的插件比使用Groovy实施的插件性能更好。

打包插件的3种方式

  • Build script:在build.gradle构建脚本中直接使用,只能在本文件内使用;
  • buildSrc project:新建一个名为buildSrc的Module使用,只能在本项目中使用;
  • Standalone project:在独立的Module中使用,可以发布到本地或者远程仓库供其他项目使用。

Build script

直接在构建脚本中编写插件代码,并应用插件。比如说在appbuild.gradle中加入如下代码:

Build script.png

按照官方文档的做法,build.gradle内引入上面的代码,会发现PluginProject2个类是无法被引入的。而且这种方案有个弊端,只能在构建脚本文件内部使用,这样就没办法提供给其他module或者project使用了。这种方案基本不会在真实项目中使用

注意:对于Gradle而言,每一个Module都是一个项目。先知道这个概念,后面会解释。


buildSrc项目

1、创建好项目之后,新建一个名称为buildSrc的Module,项目类型任意,只保留build.gradle文件和src/main目录,其余文件全部删掉。注意:名字一定要是buildSrc,否则应用插件的时候会找不到插件。修改后的目录如下:

buildSrc.png

踩过的坑:创建buildSrc这个Module的时候,如果选择了Android Library类型会有Plugin with id 'com.android.library' not found.的异常,这是因为buildSrc是Android的保留名称,只能作为plugin插件使用,后面修改buildSrc的build.gradle文件后就不报错了。如果选择Java Library类型,好像就没有这个异常,而且这个类型的文件少一些,为了方便,建议大家选择Java Library类型。

2、修改Gradle文件内容:

apply plugin: 'groovy'  //必须
apply plugin: 'maven'

dependencies {
    implementation gradleApi() //必须
    implementation localGroovy() //必须
    //如果要使用android的API,需要引用这个,实现Transform的时候会用到
    //implementation 'com.android.tools.build:gradle:3.3.0'
}

repositories {
    //google()
    jcenter()
    mavenCentral() //必须
}

注意:如果引入了com.android.tools.build:gradle:3.3.0,需要加入google()仓库,我试过只引入 jcenter()mavenCentral()仓库中,会提示找不到。

3、在main下新建groovy目录,在groovy目录下创建包名目录,在包名目录下新建一个groovy文件,并且实现org.gradle.api.Plugin接口,注意文件名需要以.groovy结尾。

4、在main下新建resources目录,在resources目录下新建META-INF目录,再在META-INF下新建gradle-plugins目录,在gradle-plugins目录下新建properties文件,比如com.zx.plugin.properties,注意:这个文件命名是没有要求的,但是要以.properties结尾。

buildSrc项目目录如下:

项目结构

CusPlugin.groovy源码如下:
注意:这里是groovy语言写的,当然也可以用java、kotlin写,他们都是基于JVM的。PluginProject是Gradle的API,所以需要先在脚本文件中配置好了再写插件实现类,否则是找不到这2个类的。

package com.zx.plugin

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


class CusPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println("this is CusPlugin")
    }
}

这里没有去定义一系列Task任务,只是简单的打印Log。在实际应用中,会定义一些任务去执行,这个后面的文章会讲。

META-INF/gradle-plugins/com.zx.plugin.properties文件的内容如下:

implementation-class=com.zx.plugin.CusPlugin

它的作用是:申明Gradle插件的具体实现类

5、在要使用插件的Module中应用,比如在appbuild.gradle中,引用插件如下:

apply plugin: 'com.android.application'
//引用自定义插件
apply plugin: 'com.zx.plugin'

注意这里引用的插件名称就是properties文件的名称。接下来如果编译正常的话,就会看见插件实现类的apply()方法中打印的日志。

运行结果

小结:

  • module名称只能为buildSrc
  • buildSrc project下的插件是自动加载。

独立的项目使用

这种方案的Module名称可以自定义,可以发布到本地或者远程仓库(jcenter、maven等)中,这样就可以供其他项目使用。

1、新建一个Module,项目类型任意,名字任意,也是只保留build.gradle文件和src/main目录,其余文件全部删掉。

2、修改Gradle文件内容:

apply plugin: 'groovy'  //必须
apply plugin: 'maven'  //要想发布到Maven,此插件必须使用


dependencies {
    implementation gradleApi() //必须
    implementation localGroovy() //必须
}
repositories {
    mavenCentral() //必须
}


def group='com.zx.cus_plugin' //组
def version='1.0.0' //版本
def artifactId='myGradlePlugin' //唯一标示


//将插件打包上传到本地maven仓库
uploadArchives {
    repositories {
        mavenDeployer {
            pom.groupId = group
            pom.artifactId = artifactId
            pom.version = version
            //指定本地maven的路径,在项目根目录下
            repository(url: uri('../repos'))
        }
    }
}

相比buildSrc方案,增加了Maven的支持和uploadArchives这样一个Task,这个Task的作用是为了将插件打包上传到本地maven仓库。注意打包文件目录是../repos,它表示的是项目根目录下,这里用了2个.,1个.表示当前module根目录,2个.表示project的根目录。

3、src/main目录下的插件实现类和properties文件与buildSrc方案是一致的。

4、在终端中执行gradle uploadArchives指令,或者展开AS右侧的Gradle,找到对应moduleuploadArchivesTask,就可以将插件部署到项目根目录的repos目录下。

uploadArchives

插件部署到本地后的目录如下:
这个repos就是你本地的Maven仓库,com/zx/cus_plugin是脚本中的group指定的,myGradlePlugin表示模块名称,是一个唯一标示,1.0.0version指定

repos目录

5、引用插件
buildSrc中,系统自动帮开发者自定义的插件提供了引用支持,但完全自定义Module的插件中,开发者就需要自己来添加自定义插件的引用支持。

在project的build.gradle文件中,添加如下脚本:

buildscript {
    repositories {
        google()
        jcenter()
        maven {
            url uri('./repos') //指定本地maven的路径,在项目根目录下
        }
    }
    dependencies {
        //classpath 'com.android.tools.build:gradle:3.3.0'
        classpath 'com.zx.cus_plugin:myGradlePlugin:1.0.0'
    }
}

注意:

  • 这里的uri只用了1个.,因为project的build.gradle文件已经在项目根目录下了

  • classpath指定的路径格式如下:
    这3个参数是在build.gradle脚本文件中申明的

classpath '[groupId]:[artifactId]:[version]' 

配置完毕后,在需要使用的Module引用插件,例如:

apply plugin: 'com.android.application'
//引用buildSrc插件,properties文件名称的方式
//apply plugin: 'com.zx.plugin'
//引用完全自定义插件,properties文件名称的方式
apply plugin: 'com.zx.cus_plugin'

小结:

  • 方案3用完全自定义Module实现自定义插件,这里是打包上传插件到本地Maven,当然你也可以发布到远程仓库,参考:gradle插件上传Jcenter与自建Maven私服

  • 上传本地Maven需要注意脚本文件中uploadArchives配置,以及引用插件时地址的配置(我在这里踩过坑),一般放在project根目录下就可以

  • 如果重新修改了插件 代码,需要重新部署uploadArchives才能在别的地方引用插件

总结

虽然官方提供了3种方案,但实际开发中只会用到后2种,有一个比较方便的做法,开发调试的时候用buildSrc project模式,等后面需要把插件共享出去等时候,再把它改为第三种方案即可。

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

推荐阅读更多精彩内容