内容简介:自定义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前端开发最佳实践
党建 / 机械工业出版社 / 2015-1 / 59.00元
本书贴近Web前端标准来介绍前端开发相关最佳实践,目的在于让前端开发工程师提高编写代码的质量,重视代码的可维护性和执行性能,让初级工程师从入门开始就养成一个良好的编码习惯。本书总共分五个部分13章,第一部分包括第1章和第2章,介绍前端开发的基本范畴和现状,并综合介绍前端开发的一些最佳实践;第二部分为第3-5章,讲解HTML相关的最佳实践,并简单介绍HTML5中新标签的使用;第三部分为第6-8章,介......一起来看看 《Web前端开发最佳实践》 这本书的介绍吧!