詳解如何使用Android Studio開(kāi)發(fā)Gradle插件
緣由
首先說(shuō)明一下為什么會(huì)有這篇文章。前段時(shí)間,插件化以及熱修復(fù)的技術(shù)很熱,Nuwa熱修復(fù)的工具NuwaGradle,攜程動(dòng)態(tài)加載技術(shù)DynamicAPK,還有希望做最輕巧的插件化框架的Small。這三個(gè)App有一個(gè)共同的地方就是大量的使用了Gradle這個(gè)強(qiáng)大的構(gòu)建工具,除了攜程的框架外,另外兩個(gè)都發(fā)布了獨(dú)立的Gradle插件提供自動(dòng)化構(gòu)建插件,或者生成熱修復(fù)的補(bǔ)丁。所以學(xué)習(xí)一下Gradle插件的編寫(xiě)還是一件十分有意義的事。
插件類(lèi)型
Gradle的插件一般有這么幾種:
- 一種是直接在項(xiàng)目中的gradle文件里編寫(xiě),這種方式的缺點(diǎn)是無(wú)法復(fù)用插件代碼,在其他項(xiàng)目中還得復(fù)制一遍代碼(或者說(shuō)說(shuō)復(fù)制一遍文件)
- 另一種是在獨(dú)立的項(xiàng)目里編寫(xiě)插件,然后發(fā)布到中央倉(cāng)庫(kù),之后直接引用就可以了,優(yōu)點(diǎn)就是可復(fù)用。就和上面的Nuwa和Small一樣。
Gradle相關(guān)語(yǔ)法
本篇文章不會(huì)詳細(xì)說(shuō)明Gradle相關(guān)的語(yǔ)法,如果要學(xué)習(xí)gradle相關(guān)的東西,請(qǐng)查看Gradle for Android
Gradle插件開(kāi)發(fā)
Gradle插件是使用Groovy進(jìn)行開(kāi)發(fā)的,而Groovy其實(shí)是可以兼容Java的。Android Studio其實(shí)除了開(kāi)發(fā)Android App外,完全可以勝任開(kāi)發(fā)Gradle插件這一工作,下面來(lái)講講具體如何開(kāi)發(fā)。
- 首先,新建一個(gè)Android項(xiàng)目。
- 之后,新建一個(gè)Android Module項(xiàng)目,類(lèi)型選擇Android Library。
- 將新建的Module中除了build.gradle文件外的其余文件全都刪除,然后刪除build.gradle文件中的所有內(nèi)容。
- 在新建的module中新建文件夾src,接著在src文件目錄下新建main文件夾,在main目錄下新建groovy目錄,這時(shí)候groovy文件夾會(huì)被Android識(shí)別為groovy源碼目錄。除了在main目錄下新建groovy目錄外,你還要在main目錄下新建resources目錄,同理resources目錄會(huì)被自動(dòng)識(shí)別為資源文件夾。在groovy目錄下新建項(xiàng)目包名,就像Java包名那樣。resources目錄下新建文件夾META-INF,META-INF文件夾下新建gradle-plugins文件夾。這樣,就完成了gradle 插件的項(xiàng)目的整體搭建,之后就是小細(xì)節(jié)了。目前,項(xiàng)目的結(jié)構(gòu)是這樣的。

打開(kāi)Module下的build.gradle文件,輸入
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
compile gradleApi()
compile localGroovy()
}
repositories {
mavenCentral()
}
下面我們?cè)诎滦陆ㄒ粋€(gè)文件,命名為PluginImpl.groovy,注意有g(shù)roovy后綴,然后在里面輸入,注意包名替換為你自己的包名。
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目錄下新建一個(gè)properties文件,注意該文件的命名就是你只有使用插件的名字,這里命名為plugin.test.properties,在里面輸入
implementation-class=cn.edu.zafu.gradle.PluginImpl
注意包名需要替換為你自己的包名。
這樣就完成了最簡(jiǎn)單的一個(gè)gradle插件,里面有一個(gè)叫testTask的Task,執(zhí)行該task后會(huì)輸出一段文字,就像當(dāng)初我們輸出HelloWorld一樣。
發(fā)布到本地倉(cāng)庫(kù)
接著,我們需要將插件發(fā)布到maven中央倉(cāng)庫(kù),我們將插件發(fā)布到本地倉(cāng)庫(kù)就好了,在module項(xiàng)目下的buidl.gradle文件中加入發(fā)布的代碼。
repositories {
mavenCentral()
}
group='cn.edu.zafu.gradle.plugin'
version='1.0.0'
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
上面的group和version的定義會(huì)被使用,作為maven庫(kù)的坐標(biāo)的一部分,group會(huì)被作為坐標(biāo)的groupId,version會(huì)被作為坐標(biāo)的version,而坐標(biāo)的artifactId組成即module名,我們讓其取一個(gè)別名moduleName。然后maven本地倉(cāng)庫(kù)的目錄就是當(dāng)前項(xiàng)目目錄下的repo目錄。
這時(shí)候,右側(cè)的gradle Toolbar就會(huì)在module下多出一個(gè)task

點(diǎn)擊uploadArchives這個(gè)Task,就會(huì)在項(xiàng)目下多出一個(gè)repo目錄,里面存著這個(gè)gradle插件。

目錄就像上圖這樣,具體目錄結(jié)構(gòu)和你的包名等一系列有關(guān),time是我的module名。
發(fā)布到本地maven倉(cāng)庫(kù)后,我們就使用它,在叫app的android項(xiàng)目下的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后面引號(hào)內(nèi)的名字就是前文plugin.test.properties文件的文件名。而class path后面引號(hào)里的內(nèi)容,就是上面grade中定義的group,version以及moduleName所共同決定的,和maven是一樣的。
同步一下gradle,右側(cè)app下other分類(lèi)下就會(huì)多出一個(gè)testTask,雙擊執(zhí)行這個(gè)Task,控制臺(tái)就會(huì)輸出剛才我們輸入的字符串

發(fā)布到Jcenter倉(cāng)庫(kù)
接下來(lái)我們將其發(fā)布到j(luò)center中央倉(cāng)庫(kù)。
在項(xiàng)目根目錄下的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'
}
在項(xiàng)目根路徑下新建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
}
}
將對(duì)應(yīng)的描述性文字修改為你自己的信息,尤其是最前面的一系列的def定義,然后在gradle.properties文件中加入BINTRAY_USER和BINTRAY_KEY。
在你的module中apply該grade文件
apply from: '../bintray.gradle'
右側(cè)的gradle的toolbar就會(huì)多出幾個(gè)task

之后我們先運(yùn)行other下的install這個(gè)task,再執(zhí)行bintrayUpload這個(gè)task,如果不出意外,就上傳了,之后不要忘記到后臺(tái)add to jcenter。成功add到j(luò)center之后就會(huì)有l(wèi)ink to jcenter的字樣

耐心等待add to center成功的消息,之后就可以直接引用了,將module下的gradle文件maven部分的定義
maven {
url uri('../repo')
}
前面加入
jcenter()
最終的內(nèi)容如下
buildscript {
repositories {
jcenter()
maven {
url uri('../repo')
}
}
dependencies {
classpath 'cn.edu.zafu.gradle.plugin:time:1.0.0'
}
}
apply plugin: 'plugin.test'
就是這么簡(jiǎn)單,再次運(yùn)行一下測(cè)試下是否成功。
最佳實(shí)踐
最佳實(shí)踐的來(lái)源是源自multidex,為什么呢,因?yàn)樽罱?dāng)方法數(shù)超了之后,如果選擇multidex,編譯的過(guò)程就會(huì)慢很多很多,為了檢測(cè)到底是哪一步的耗時(shí),需要編寫(xiě)一個(gè)插件來(lái)統(tǒng)計(jì)各個(gè)task執(zhí)行的時(shí)間,因此就有了這么一個(gè)最佳實(shí)踐。
在PluginImpl同級(jí)目錄下新建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())
}
完成后打包發(fā)布到j(luò)center()。之后你只要引用了該插件,就會(huì)統(tǒng)計(jì)各個(gè)task執(zhí)行的時(shí)間,比如運(yùn)行app,就會(huì)輸出像下面的信息。

最佳實(shí)踐的末尾,推廣一下這個(gè)插件,這個(gè)插件我已經(jīng)將其發(fā)布到j(luò)center倉(cāng)庫(kù),如果要使用的話(huà)加入下面的代碼即可
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'cn.edu.zafu.gradle.plugin:time:1.0.0'
}
}
apply plugin: 'plugin.time'
傳遞參數(shù)
上面的是小試牛刀了下,接下來(lái)我們需要獲得自定義的參數(shù)。
首先按照上面的步驟新建一個(gè)module。新建PluginExtension.groovy,輸入
public class PluginExtension {
def param1 = "param1 defaut"
def param2 = "param2 defaut"
def param3 = "param3 defaut"
}
然后我們希望能傳入嵌套的參數(shù),再新建一個(gè)PluginNestExtension.groovy,輸入
public class PluginNestExtension {
def nestParam1 = "nestParam1 defaut"
def nestParam2 = "nestParam2 defaut"
def nestParam3 = "nestParam3 defaut"
}
然后新建一個(gè)CustomTask.groovy,繼承DefaultTask類(lèi),使用 @TaskAction注解標(biāo)注實(shí)現(xiàn)的方法
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}"
}
}
只是做了拿到了參數(shù),然后做最簡(jiǎn)單的輸出操作,使用 ${project.pluginExt.param1}和 ${project.pluginExt.nestExt.nestParam1}等拿到外部的參數(shù)。
別忘了在META-INF/gradle-plugins目錄下新建properties文件指定插件的接口實(shí)現(xiàn)類(lèi)。
復(fù)制之前新建的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)
}
}
將插件發(fā)布到本地maven后,進(jìn)行引用。
buildscript {
repositories {
maven {
url uri('../repo')
}
}
dependencies {
classpath 'cn.edu.zafu.gradle.plugin:test:1.0.0'
}
}
apply plugin: 'plugin.test'
定義外部參數(shù),這里我們定義了param1,param2,nestParam1,nestParam2,此外param3和nestParam3保持默認(rèn)。
pluginExt {
param1 = 'app param1'
param2 = 'app param2'
nestExt{
nestParam1='app nestParam1'
nestParam2='app nestParam2'
}
}
同步一下gradle,執(zhí)行customTask。

上面的代碼很簡(jiǎn)單,不用解釋也能看到,所以不再解釋了。
源碼
最后上本篇文章的源碼 :GradlePlugin_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 為Android Studio編寫(xiě)自定義Gradle插件的教程
- 詳解Android Gradle插件3.0挖坑日記
- Android gradle插件打印時(shí)間戳的方法詳解
- Android Studio Gradle插件版本與Gradle版本之間的對(duì)應(yīng)關(guān)系
- AndroidStudio升級(jí)4.1坑(無(wú)法啟動(dòng)、插件plugin不好用、代碼不高亮)
- AndroidStudio升級(jí)4.1后啟動(dòng)失敗Plugin問(wèn)題解決
- 解決Android Studio4.1沒(méi)有Gsonfomat插件,Plugin “GsonFormat” is incompatible的問(wèn)題
- Android自定義Gradle插件的詳細(xì)過(guò)程
- Android?Studio?中Gradle配置sonarqube插件(推薦)
- Android?Gradle?插件自定義Plugin實(shí)現(xiàn)注意事項(xiàng)
相關(guān)文章
Android ListView自動(dòng)生成列表?xiàng)l目的實(shí)例
下面小編就為大家分享一篇Android ListView自動(dòng)生成列表?xiàng)l目的實(shí)例,具有很好的 參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Android開(kāi)發(fā)使用URLConnection進(jìn)行網(wǎng)絡(luò)編程詳解
這篇文章主要介紹了Android開(kāi)發(fā)使用URLConnection進(jìn)行網(wǎng)絡(luò)編程,結(jié)合實(shí)例形式分析了Android URLConnection對(duì)象創(chuàng)建、屬性、方法及相關(guān)使用技巧,需要的朋友可以參考下2018-01-01
Android 開(kāi)發(fā)使用PopupWindow實(shí)現(xiàn)加載等待界面功能示例
這篇文章主要介紹了Android 開(kāi)發(fā)使用PopupWindow實(shí)現(xiàn)加載等待界面功能,結(jié)合實(shí)例形式分析了Android使用PopupWindow組件實(shí)現(xiàn)加載等待界面功能相關(guān)布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-05-05
Android使用自定義控件HorizontalScrollView打造史上最簡(jiǎn)單的側(cè)滑菜單
側(cè)滑菜單一般都會(huì)自定義ViewGroup,然后隱藏菜單欄,當(dāng)手指滑動(dòng)時(shí),通過(guò)Scroller或者不斷的改變leftMargin等實(shí)現(xiàn);多少都有點(diǎn)復(fù)雜,完成以后還需要對(duì)滑動(dòng)沖突等進(jìn)行處理,今天給大家?guī)?lái)一個(gè)簡(jiǎn)單的實(shí)現(xiàn),史上最簡(jiǎn)單有點(diǎn)夸張,但是的確是我目前遇到過(guò)的最簡(jiǎn)單的一種實(shí)現(xiàn)2016-02-02
Android 實(shí)現(xiàn)錨點(diǎn)定位思路詳解
本篇文章就使用tablayout、scrollview來(lái)實(shí)現(xiàn)android錨點(diǎn)定位的功能。通過(guò)<a href="#head" rel="external nofollow" > 去設(shè)置頁(yè)面內(nèi)錨點(diǎn)定位跳轉(zhuǎn)。具體實(shí)現(xiàn)思路大家跟隨腳本之家小編一起通過(guò)本文看下吧2018-07-07
Android實(shí)現(xiàn)讀寫(xiě)JSON數(shù)據(jù)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)讀寫(xiě)JSON數(shù)據(jù)的方法,以完整實(shí)例形式分析了Android解析及生成json數(shù)據(jù)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10

