官方文档给出了详细的实现步骤,笔者 将参考官方文档作一些基础介绍,额外增长一个实例:经过自定义插件修改编译后的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
发布插件代码,新增/修改的代码断点才能起做用