首先说明一下为什么会有这篇文章。前段时间,插件化以及热修复的技术很热,Nuwa热修复的工具NuwaGradle,携程动态加载技术DynamicAPK,还有希望做最轻巧的插件化框架的Small。这三个App有一个共同的地方就是大量的使用了Gradle这个强大的构建工具,除了携程的框架外,另外两个都发布了独立的Gradle插件提供自动化构建插件,或者生成热修复的补丁。所以学习一下Gradle插件的编写还是一件十分有意义的事。
Gradle的插件一般有这么几种:
本篇文章不会详细说明Gradle相关的语法,如果要学习gradle相关的东西,请查看Gradle for Android
Gradle插件是使用Groovy进行开发的,而Groovy其实是可以兼容Java的。Android Studio其实除了开发Android App外,完全可以胜任开发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中央仓库。参考之前写的一篇文章 使用Android Studio将开源库发布到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。
上面的代码很简单,不用解释也能看到,所以不再解释了。
最后上本篇文章的源码
http://download.csdn.net/detail/sbsujjbcy/9451566