官方文檔給出了詳細的實現步驟,筆者 將參考官方文檔作一些基礎介紹,額外增長一個實例:經過自定義插件修改編譯後的class文件,本文按照如下三個方面進行講解html
- 插件基礎介紹
- 三種插件的打包方式
- 實例Demo&Debug調試
根據插件官方文檔定義,插件打包了可重用的構建邏輯,能夠適用不一樣的項目和構建。java
Gradle 提供了不少官方插件,用於支持Java、Groovy等工程的構建和打包。同時也提供了自定義插件機制,讓每一個人均可以經過插件來實現特定的構建邏輯,並能夠把這些邏輯打包起來,分享給其餘人。android
插件的源碼能夠是用Groovy、Scale、Java三種語言,筆者對Scale不熟悉,對Groovy也略知一二。Groovy用於實現構建生命週期(如Task的依賴)有關邏輯,Java用於實現核心邏輯,表現爲Groovy調用Java代碼git
另外,還有不少項目使用Eclipse 或者Maven進行開發構建,用Java實現核心業務代碼,將有利於實現快速遷移。github
筆者編寫自定義插件相關代碼時,對不少GradlePluginForAndroid
相關api 不熟悉,例如Transform
、TransformOutputProvider
等,不要緊,官方文檔gradle-plugin-android-api 將會是你最好的學習教程api
把插件寫在build.gradle 文件中,通常用於簡單的邏輯,只在改build.gradle 文件中可見,筆者經常使用來作原型調試。在咱們指定的module build.gradle 中:閉包
/** * 分別定義Extension1 和 Extension2 類,申明參數傳遞變量 */
class Extension1 {
String testVariable1 = null
}
class Extension2 {
String testVariable2 = null
}
/** * 插件入口類 */
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//利用Extension建立e1 e2 閉包,用於接受外部傳遞的參數值
project.extensions.create('e1', Extension1)
project.extensions.create('e2', Extension2)
//建立readExtension task 執行該task 進行參數值的讀取以及自定義邏輯...
project.task('readExtension') << {
println 'e1 = ' + project['e1'].testVariable1
println 'e2 = ' + project['e2'].testVariable2
}
}
}
/** * 依賴咱們剛剛自定義的TestPlugin,注意 使用e1 {} || e2{} 必定要放在apply plugin:TestPlugin 後面, 由於 app plugin:TestPlugin * 會執行 Plugin的apply 方法,進而利用Extension 將e1 、e2 和 Extension1 Extension2 綁定,編譯器纔不會報錯 */
apply plugin: TestPlugin
e1 {
testVariable1 = 'testVariable1'
}
e2 {
testVariable2 = 'testVariable2'
}
複製代碼
相關注釋說明已經在代碼中簡單說明,若是讀者依然不熟悉或者想了解更多內容,能夠在api文檔中進行查閱。 而後執行readExtension
task 便可app
./gradlew -p moduledir readExtension --stacktrace
複製代碼
運行結果 maven
將插件源代碼放在rootProjectDir/buildScr/scr/main/groovy
中,只對該項目中可見,適用於邏輯較爲複雜,但又不須要外部可見的插件,本文不介紹,有興趣能夠參考此處ide
一個獨立的Groovy 和Java項目,能夠把這個項目打包成jar文件包,一個jar文件包還能夠包含多個插件入口,能夠將文件包發佈到託管平臺上,共其餘人使用。
其實,IntelliJIEDA 開發插件要比Android Studio要方便一點,由於有對應的Groovy module模板,但若是咱們瞭解IDEA項目文件結構,就不會受到這個侷限,無非就是一個build.gradle 構建文件夾scr源碼文件夾
在Android Studio中新建 Java Library
module uploader
(moduleName 不重要,根據實際狀況定義)
修改項目文件夾
修改build.gradle 文件
//removed java plugin
apply plugin: 'groovy'
apply plugin: 'maven'
repositories {
mavenCentral()
}
dependencies {
compile gradleApi()//gradle sdk
compile localGroovy()//groovy sdk
compile fileTree(dir: 'libs', include: ['*.jar'])
}
uploadArchives {
repositories {
mavenDeployer {
//設置插件的GAV參數
pom.groupId = 'cn.andaction.plugin'
pom.version = '1.0.0'
//文件發佈到下面目錄
repository(url: uri('../repo'))
}
}
}
複製代碼
創建對應文件
├── build.gradle
├── libs
├── plugin.iml
└── src
└── main
├── groovy
│ └── cn
│ └── andaction
│ └── uploader
│ ├── XXXPlugin.groovy
│ └── YYYY.groovy
└── resources
└── META-INF
└── gradle-plugins
└── uploader.properties
複製代碼
.groovy
後綴,IDE纔會正常識別另外,關於uploader.properties ,寫過java的同窗應該知道,這是一個java的properties文件,是key=value
的格式,這個文件內容以下
implementation-class=cn.andaction.uploader.XXXPlugin.groovy
複製代碼
用於指定插件入口類,其中apply plugin: '${當前配置文件名}
自定義
gradle-plugin
並利用javassist 類庫工具修改指定編譯後的class文件
筆者參考了經過自定義Gradle插件修改編譯後的class文件
預備知識
buid.gradle 增長類庫依賴
compile 'com.android.tools.build:gradle:3.0.1'
compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
複製代碼
自定義Transform
public class PreDexTransform extends Transform {
private Project project
/** * 構造函數 咱們將Project 保存下來備用 * @param project */
PreDexTransform(Project project) {
this.project = project
}
....
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
//transformInvocation.inputs 有兩種類型,一種是目錄,一種是jar包 分開對其進行遍歷
transformInvocation.inputs.each { TransformInput input ->
// 對類型爲文件夾 的input進行遍歷 :對應的class字節碼文件
// 借用JavaSsist 對文件夾的class 字節碼 進行修改
input.directoryInputs.each { DirectoryInput directoryInput ->
TestInject.injectDir(directoryInput.file.absolutePath, 'cn.andaction.plugin')
File des = transformInvocation.getOutputProvider().getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file, des)
}
// 對類型爲jar的input進行遍歷 : 對應三方庫等
input.jarInputs.each { JarInput jarInput ->
def jarName = jarInput.name
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if (jarName.endsWith('.jar')) {
jarName = jarName.substring(0, jarName.length() - 4) // '.jar'.length == 4
}
File dest = transformInvocation.getOutputProvider().getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
// 將輸入內容複製到輸出
FileUtils.copyFile(jarInput.file, dest)
}
}
super.transform(transformInvocation)
}
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
super.transform(context, inputs, referencedInputs, outputProvider, isIncremental)
}
}
複製代碼
對directoryInputs 文件夾下的class文件遍歷,找到符合須要的.class 文件,經過javassit 類庫對字節碼文件進行修改
TestInject.groovy
File dir = new File(path)
classPool.appendClassPath(path)
if (dir.isDirectory()) {
dir.eachFileRecurse { File file ->
String filePath = file.path
// 這裏咱們指定修改TestInjectModel.class字節碼,在構造函數中增長一行i will inject
if (filePath.endsWith('.class')
&& filePath.endsWith('TestInjectModel.class')) {
// 判斷當前目錄是否在咱們的應用包裏面
int index = filePath.indexOf(packageName.replace('.',File.separator))
if (index != -1) {
int end = filePath.length() - 6 // '.class'.length = 6
String className = filePath.substring(index, end)
.replace('\\', '.')
.replace('/', '.')
// 開始修改class文件
CtClass ctClass = classPool.getCtClass(className)
// 拿到CtClass後能夠對 class 作修改操做(addField addMethod ..)
if (ctClass.isFrozen()) {
ctClass.defrost()
}
CtConstructor[] constructors = ctClass.getDeclaredConstructors()
if (null == constructors || constructors.length == 0) {
// 手動建立一個構造函數
CtConstructor constructor = new CtConstructor(new CtClass[0], ctClass)
constructor.insertBeforeBody(injectStr)
//constructor.insertBefore() 會增長super(),且插入的代碼在super()前面 ctClass.addConstructor(constructor)
} else {
constructors[0].insertBeforeBody(injectStr)
}
ctClass.writeFile(path)
ctClass.detach()
}
}
}
}
複製代碼
發佈插件代碼到本地
./gradlew -p moduleDir/ clean build uploadArchives -stacktrace
複製代碼
運行測試
build.gradle
repositories {
maven {
url 'file:your-project-dir/repo/'
}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'cn.andaction.plugin:uploader:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
複製代碼
apply plugin: 'uploader'
複製代碼
修改代碼
TestInjectModel.java
,空實現onCreate
方法調用new TestInjectModle()
執行make project
插件調試
參考Android Studio 調試Gradle-plugin
注意,在修改插件源碼後,須要從新執行uploadArchives
發佈插件代碼,新增/修改的代碼斷點才能起做用