详解如何使用Android Studio开发Gradle插件
缘由
首先说明一下为什么会有这篇文章。前段时间,插件化以及热修复的技术很热,nuwa热修复的工具nuwagradle,携程动态加载技术dynamicapk,还有希望做最轻巧的插件化框架的small。这三个app有一个共同的地方就是大量的使用了gradle这个强大的构建工具,除了携程的框架外,另外两个都发布了独立的gradle插件提供自动化构建插件,或者生成热修复的补丁。所以学习一下gradle插件的编写还是一件十分有意义的事。
插件类型
gradle的插件一般有这么几种:
- 一种是直接在项目中的gradle文件里编写,这种方式的缺点是无法复用插件代码,在其他项目中还得复制一遍代码(或者说说复制一遍文件)
- 另一种是在独立的项目里编写插件,然后发布到*仓库,之后直接引用就可以了,优点就是可复用。就和上面的nuwa和small一样。
gradle相关语法
本篇文章不会详细说明gradle相关的语法,如果要学习gradle相关的东西,请查看gradle for android
gradle插件开发
gradle插件是使用groovy进行开发的,而groovy其实是可以兼容java的。android studio其实除了开发android app外,完全可以胜任开发gradle插件这一工作,下面来讲讲具体如何开发。
- 首先,新建一个android项目。
- 之后,新建一个android module项目,类型选择android library。
- 将新建的module中除了build.gradle文件外的其余文件全都删除,然后删除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 插件的项目的整体搭建,之后就是小细节了。目前,项目的结构是这样的。
打开module下的build.gradle文件,输入
apply plugin: 'groovy' apply plugin: 'maven' dependencies { compile gradleapi() compile localgroovy() } repositories { mavencentral() }
下面我们在包名下新建一个文件,命名为pluginimpl.groovy,注意有groovy后缀,然后在里面输入,注意包名替换为你自己的包名。
package cn.edu.zafu.gradle import org.gradle.api.plugin import org.gradle.api.project public class pluginimpl implements plugin<project> { void apply(project project) { project.task('testtask') << { println "hello gradle plugin" } } }
然后在resources/meta-inf/gradle-plugins目录下新建一个properties文件,注意该文件的命名就是你只有使用插件的名字,这里命名为plugin.test.properties,在里面输入
implementation-class=cn.edu.zafu.gradle.pluginimpl
注意包名需要替换为你自己的包名。
这样就完成了最简单的一个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')) } } }
上面的group和version的定义会被使用,作为maven库的坐标的一部分,group会被作为坐标的groupid,version会被作为坐标的version,而坐标的artifactid组成即module名,我们让其取一个别名modulename。然后maven本地仓库的目录就是当前项目目录下的repo目录。
这时候,右侧的gradle toolbar就会在module下多出一个task
点击uploadarchives这个task,就会在项目下多出一个repo目录,里面存着这个gradle插件。
目录就像上图这样,具体目录结构和你的包名等一系列有关,time是我的module名。
发布到本地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'
apply plugin后面引号内的名字就是前文plugin.test.properties文件的文件名。而class path后面引号里的内容,就是上面grade中定义的group,version以及modulename所共同决定的,和maven是一样的。
同步一下gradle,右侧app下other分类下就会多出一个testtask,双击执行这个task,控制台就会输出刚才我们输入的字符串
发布到jcenter仓库
接下来我们将其发布到jcenter*仓库。
在项目根目录下的build.gradle文件中加入。
dependencies { classpath 'com.android.tools.build:gradle:2.0.0-beta6' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' classpath 'com.github.dcendents:android-maven-plugin:1.2' }
在项目根路径下新建bintray.gradle文件,输入
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' def projectname = "timeplugin" def mavendesc = 'your desc' def baseurl = 'https://github.com/yourbaseurl' def siteurl = baseurl def giturl = "${baseurl}/yourgiturl" def issueurl = "${baseurl}/yourgitissueurl" def licenseids = ['apache-2.0'] def licensenames = ['the apache software license, version 2.0'] def licenseurls = ['http://www.apache.org/licenses/license-2.0.txt'] def inception = '2016' def username = 'lizhangqu' install { repositories { maveninstaller { pom.project { // description name projectname description mavendesc url siteurl // archive groupid project.group artifactid archivesbasename version project.version // license inceptionyear inception licenses { licensenames.eachwithindex { ln, li -> license { name ln url licenseurls[li] } } } developers { developer { name username } } scm { connection giturl developerconnection giturl url siteurl } } } } } task sourcesjar(type: jar) { from sourcesets.main.allgroovy classifier = 'sources' } task javadocjar(type: jar, dependson: groovydoc) { from groovydoc.destinationdir classifier = 'javadoc' } artifacts { archives javadocjar archives sourcesjar } bintray { user = bintray_user key = bintray_key configurations = ['archives'] pkg { repo = 'maven' name = projectname desc = mavendesc websiteurl = siteurl issuetrackerurl = issueurl vcsurl = giturl labels = ['gradle', 'plugin', 'time'] licenses = licenseids publish = true publicdownloadnumbers = true } }
将对应的描述性文字修改为你自己的信息,尤其是最前面的一系列的def定义,然后在gradle.properties文件中加入bintray_user和bintray_key。
在你的module中apply该grade文件
apply from: '../bintray.gradle'
右侧的gradle的toolbar就会多出几个task
之后我们先运行other下的install这个task,再执行bintrayupload这个task,如果不出意外,就上传了,之后不要忘记到后台add to jcenter。成功add到jcenter之后就会有link to jcenter的字样
耐心等待add to center成功的消息,之后就可以直接引用了,将module下的gradle文件maven部分的定义
maven { url uri('../repo') }
前面加入
jcenter()
最终的内容如下
buildscript { repositories { jcenter() maven { url uri('../repo') } } dependencies { classpath 'cn.edu.zafu.gradle.plugin:time:1.0.0' } } apply plugin: 'plugin.test'
就是这么简单,再次运行一下测试下是否成功。
最佳实践
最佳实践的来源是源自multidex,为什么呢,因为最近当方法数超了之后,如果选择multidex,编译的过程就会慢很多很多,为了检测到底是哪一步的耗时,需要编写一个插件来统计各个task执行的时间,因此就有了这么一个最佳实践。
在pluginimpl同级目录下新建timelistener.groovy文件。输入
package cn.edu.zafu.gradle import org.gradle.buildlistener import org.gradle.buildresult import org.gradle.api.task import org.gradle.api.execution.taskexecutionlistener import org.gradle.api.initialization.settings import org.gradle.api.invocation.gradle import org.gradle.api.tasks.taskstate import org.gradle.util.clock class timelistener implements taskexecutionlistener, buildlistener { private clock clock private times = [] @override void beforeexecute(task task) { clock = new org.gradle.util.clock() } @override void afterexecute(task task, taskstate taskstate) { def ms = clock.timeinms times.add([ms, task.path]) task.project.logger.warn "${task.path} spend ${ms}ms" } @override void buildfinished(buildresult result) { println "task spend time:" for (time in times) { if (time[0] >= 50) { printf "%7sms %s\n", time } } } @override void buildstarted(gradle gradle) {} @override void projectsevaluated(gradle gradle) {} @override void projectsloaded(gradle gradle) {} @override void settingsevaluated(settings settings) {} }
然后将pluginimpl文件中的apply方法修改为
void apply(project project) { project.gradle.addlistener(new timelistener()) }
完成后打包发布到jcenter()。之后你只要引用了该插件,就会统计各个task执行的时间,比如运行app,就会输出像下面的信息。
最佳实践的末尾,推广一下这个插件,这个插件我已经将其发布到jcenter仓库,如果要使用的话加入下面的代码即可
buildscript { repositories { jcenter() } dependencies { classpath 'cn.edu.zafu.gradle.plugin:time:1.0.0' } } apply plugin: 'plugin.time'
传递参数
上面的是小试牛刀了下,接下来我们需要获得自定义的参数。
首先按照上面的步骤新建一个module。新建pluginextension.groovy,输入
public class pluginextension { def param1 = "param1 defaut" def param2 = "param2 defaut" def param3 = "param3 defaut" }
然后我们希望能传入嵌套的参数,再新建一个pluginnestextension.groovy,输入
public class pluginnestextension { def nestparam1 = "nestparam1 defaut" def nestparam2 = "nestparam2 defaut" def nestparam3 = "nestparam3 defaut" }
然后新建一个customtask.groovy,继承defaulttask类,使用 @taskaction注解标注实现的方法
public 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}等拿到外部的参数。
别忘了在meta-inf/gradle-plugins目录下新建properties文件指定插件的接口实现类。
复制之前新建的pluginimpl.groovy到包下,修改apply方法
public class pluginimpl implements plugin<project> { void apply(project project) { project.extensions.create('pluginext', pluginextension) project.pluginext.extensions.create('nestext', pluginnestextension) project.task('customtask', type: customtask) } }
将插件发布到本地maven后,进行引用。
buildscript { repositories { maven { url uri('../repo') } } dependencies { classpath 'cn.edu.zafu.gradle.plugin:test:1.0.0' } } apply plugin: 'plugin.test'
定义外部参数,这里我们定义了param1,param2,nestparam1,nestparam2,此外param3和nestparam3保持默认。
pluginext { param1 = 'app param1' param2 = 'app param2' nestext{ nestparam1='app nestparam1' nestparam2='app nestparam2' } }
同步一下gradle,执行customtask。
上面的代码很简单,不用解释也能看到,所以不再解释了。
源码
最后上本篇文章的源码 :gradleplugin_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
详解如何使用Android Studio开发Gradle插件
-
【Android新手指引】Android Studio如何使用Gradle进行多渠道打包
-
Android Studio使用教程(五):Gradle命令详解和导入第三方包
-
如何使用Android Studio 快速开发ijkplayer 播放器?
-
使用Android Studio开发widget安卓桌面插件
-
Android Studio使用教程(五):Gradle命令详解和导入第三方包
-
【Android新手指引】Android Studio如何使用Gradle进行多渠道打包
-
如何使用Android Studio 快速开发ijkplayer 播放器?