内容简介:自定义Gradle-Plugin 插件
官方文档给出了详细的实现步骤,笔者 将参考官方文档做一些基础介绍,额外增加一个实例: 通过自定义插件修改编译后的class文件 ,本文按照以下三个方面进行讲解
- 插件基础介绍
- 三种插件的打包方式
- 实例Demo&Debug调试
插件基础介绍
根据插件官方文档定义,插件打包了可重用的构建逻辑,可以适用不同的项目和构建。
Gradle 提供了很多官方插件,用于支持 Java 、Groovy等工程的构建和打包。同时也提供了自定义插件机制,让每个人都可以通过插件来实现特定的构建逻辑,并可以把这些逻辑打包起来,分享给其他人。
插件的源码可以是用Groovy、Scale、Java三种语言,笔者对Scale不熟悉,对Groovy也略知一二。 Groovy用于实现构建生命周期(如Task的依赖)有关逻辑,Java用于实现核心逻辑,表现为Groovy调用Java代码
另外,还有很多项目使用Eclipse 或者Maven进行开发构建,用Java实现核心业务代码,将有利于实现快速迁移。
三种插件的实现方式
笔者编写自定义插件相关代码时,对很多 GradlePluginForAndroid
相关api 不熟悉,例如 Transform
、 TransformOutputProvider
等,没关系,官方文档 gradle-plugin-android-api 将会是你最好的学习教程
Build Script
把插件写在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 即可
./gradlew -p moduledir readExtension --stacktrace
运行结果
buildSrc 项目
将插件源代码放在 rootProjectDir/buildScr/scr/main/groovy
中,只对该项目中可见,适用于逻辑较为复杂,但又不需要外部可见的插件,本文不介绍,有兴趣可以参考此处
独立项目
一个独立的Groovy 和Java项目,可以把这个项目打包成jar文件包,一个jar文件包还可以包含多个插件入口,可以将文件包发布到托管平台上,共其他人使用。
其实,IntelliJIEDA 开发插件要比Android Studio要方便一点,因为有对应的Groovy module模板,但如果我们了解IDEA项目文件结构,就不会受到这个局限,无非就是一个build.gradle 构建文件夹scr源码文件夹
-
在Android Studio中新建
Java Library
moduleuploader
(moduleName 不重要,根据实际情况定义) -
修改项目文件夹
- 移除java文件夹,因为在这个项目中用不到java代码
- 添加Groovy文件夹,主要的代码文件放在这里
- 添加resource文件夹,存放用于标识gradle插件的meta-data
-
修改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文件夹中的类,一定要修改成
.groovy
后缀,IDE才会正常识别 - resource/META-INF/gradle-plugins这个文件夹结构是强制要求的,否则不能识别成插件
另外,关于uploader.properties ,写过java的同学应该知道,这是一个java的properties文件,是
key=value
的格式,这个文件内容如下implementation-class=cn.andaction.uploader.XXXPlugin.groovy
用于指定插件入口类,其中apply plugin: '${当前配置文件名}
- Groovy文件夹中的类,一定要修改成
实例Demo
自定义 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
,空实现 - app入口类
onCreate
方法调用new TestInjectModle()
- 新增
-
执行
make project
-
-
插件调试
参考 Android Studio 调试Gradle-plugin
注意,在修改插件源码后,需要重新执行 uploadArchives
发布插件代码,新增/修改的代码断点才能起作用
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Maven 自定义插件
- 自定义插件
- zabbix 日志监控(自定义插件开发)
- 一起入门gradle自定义插件编写(一)
- Mybatis 自定义拦截器与插件开发
- iOS持续集成(三)——fastlane 自定义插件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Caching
Duane Wessels / O'Reilly Media, Inc. / 2001-6 / 39.95美元
On the World Wide Web, speed and efficiency are vital. Users have little patience for slow web pages, while network administrators want to make the most of their available bandwidth. A properly design......一起来看看 《Web Caching》 这本书的介绍吧!