动态编译入门(gradle Transform Demo)
概述
现在市面上的插件化框架,热修复框架几乎都使用了动态编译技术。
动态编译的实质是,使用gradle transform api,在项目构建过程的class文件转成dex文件之前,通过自定义插件,进行class字节码处理。
本文主要是通过走一遍简单Demo实现流程,让读者能对动态编译有一个大概的了解。
如对一些细节知识有更多需求的读者就需要自行学习了。
简单Demo
本文的Demo,通过动态编译实现在代码中插入一行代码。
主要实现步骤如下:
- 实现gradle Plugin。
- 实现Transform,并且在Plugin中注册。
- Plugin编译,并且上传到本地仓库。
- app项目应用Plugin,通过插件实现动态编译。
- 运行项目,查看动态编译结果。
Demo结果
public class PluginTestClass {public void init(){System.out.println("PluginTestClass init");//这里将会插入System.out.println("我是插入的代码");}
}
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);PluginTestClass pluginTestClass=new PluginTestClass();pluginTestClass.init();}
}
执行结果:
2020-03-29 09:04:37.029 26361-26361/? I/System.out: PluginTestClass init
2020-03-29 09:04:37.029 26361-26361/? I/System.out: 我是插入的代码
testplugin模块
这个模块主要实现了两个内容:
- 实现插件
- 实现Transform,编辑class文件,插入代码。
build.gradle
apply plugin: 'groovy'
apply plugin: 'maven'
apply plugin: 'java'dependencies {compile gradleApi()//gradle sdkcompile localGroovy()//groovy sdkimplementation 'com.android.tools.build:gradle:3.6.1'implementation 'org.javassist:javassist:3.27.0-GA'//用于编辑class文件
}repositories {mavenCentral()
}//提交仓库到本地目录
def version = "1.0.0";
def artifactId = "testplugin";
def groupId = "com.example.plugin.test";
uploadArchives {repositories {mavenDeployer {repository(url: uri('./repo')) {pom.groupId = groupIdpom.artifactId = artifactIdpom.version = version}}}
}
TestPlugin.java
public class TestPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {System.out.println("这是自定义插件!");project.getExtensions().findByType(BaseExtension.class).registerTransform(new TestTransform());}
}
TestTransform.java
public class TestTransform extends Transform {//用于指明本Transform的名字,也是代表该Transform的task的名字@Override public String getName() {return "TestTransform";}//用于指明Transform的输入类型,可以作为输入过滤的手段。@Override public Set<QualifiedContent.ContentType> getInputTypes() {return TransformManager.CONTENT_CLASS;}//用于指明Transform的作用域@Override public Set<? super QualifiedContent.Scope> getScopes() {return TransformManager.SCOPE_FULL_PROJECT;}//是否增量编译@Override public boolean isIncremental() {return false;}@Override public void transform(TransformInvocation invocation) {System.out.println("TestTransform transform");for (TransformInput input : invocation.getInputs()) {//遍历jar文件 对jar不操作,但是要输出到out路径input.getJarInputs().parallelStream().forEach(jarInput -> {File src = jarInput.getFile();System.out.println("input.getJarInputs fielName:" + src.getName());File dst = invocation.getOutputProvider().getContentLocation(jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(),Format.JAR);try {FileUtils.copyFile(src, dst);} catch (IOException e) {throw new RuntimeException(e);}});//遍历文件,在遍历过程中input.getDirectoryInputs().parallelStream().forEach(directoryInput -> {File src = directoryInput.getFile();System.out.println("input.getDirectoryInputs fielName:" + src.getName());File dst = invocation.getOutputProvider().getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(),directoryInput.getScopes(), Format.DIRECTORY);try {scanFilesAndInsertCode(src.getAbsolutePath());FileUtils.copyDirectory(src, dst);} catch (Exception e) {System.out.println(e.getMessage());}});}}private void scanFilesAndInsertCode(String path) throws Exception {ClassPool classPool = ClassPool.getDefault();classPool.appendClassPath(path);//将当前路径加入类池,不然找不到这个类CtClass ctClass = classPool.getCtClass("com.example.testplugin.PluginTestClass");if (ctClass == null) {return;}if (ctClass.isFrozen()) {ctClass.defrost();}CtMethod ctMethod = ctClass.getDeclaredMethod("init");String insetStr = "System.out.println(\"我是插入的代码\");";ctMethod.insertAfter(insetStr);//在方法末尾插入代码ctClass.writeFile(path);ctClass.detach();//释放}
}
TestPlugin.properties
这个文件注意,一定要在这个目录下,否则会找不到插件。
implementation-class=com.example.testplugin.TestPlugin
使用gradle将上传到私有仓库
由于在build.gradle中配置的仓库地址是"./repo"。
最终执行结束后,项目中可以看到这个仓库:
app模块
这个模块主要实现两个内容
- 应用插件
- 实现demo代码
TransformTest/build.gradle
需要再根目录添加插件的仓库地址和依赖插件。
笔者在testplugin项目中生成repo仓库后,会再复制一份到根目录,这份根目录的repo才是真正使用到的仓库。
代码多一个copy的过程的目的:主要是避免testplugin/repo仓库删除的时候项目没法编译。
buildscript {repositories {google()jcenter()maven {url uri('./repo')//添加依赖仓库}}dependencies {classpath 'com.android.tools.build:gradle:3.6.1'classpath 'com.example.plugin.test:testplugin:1.0.0'//依赖插件项目}
}allprojects {repositories {google()jcenter()}
}task clean(type: Delete) {delete rootProject.buildDir
}
TransformTest/app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'TestPlugin'//应用插件——后面自动生成的代码省略——
PluginTestClass.java
public class PluginTestClass {public void init(){System.out.println("PluginTestClass init");//这里将会插入System.out.println("我是插入的代码");}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);PluginTestClass pluginTestClass=new PluginTestClass();pluginTestClass.init();}
}
动态编译入门(gradle Transform Demo)相关推荐
- 【java】深入理解Java的动态编译
文章目录 1.概述 2. 前提 3. 基本原理# 4. JDK动态编译 4.1 实现JavaFileObject 4.2 实现ClassLoader 4.3 实现JavaFileManager 4.4 ...
- 注解、反射、动态编译、字节码操作
注解.反射.动态编译.字节码操作 前言:本篇博客将介绍Java中注解的定义.使用以及反射对Java动态性的支持和Java字节码操作,通过本篇内容,读者将对Java知识有更加深刻的理解,同时为后面And ...
- 零基础AJAX入门(含Demo演示源文件)
零基础AJAX入门(含Demo演示源文件) 作者:一点一滴的Beer 个人主页:http://www.cnblogs.com/beer 摘要:因为笔者的大四毕业设计是做WebGIS系统,用过Web版 ...
- C#发现之旅第十四讲 基于动态编译的VB.NET脚本引擎
本章说明 在前面章节中,笔者使用了反射和动态编译技术实现了快速ORM框架,在本章中笔者将继续使用这些技术来实现一个VB.NET的脚本引擎,使得人们在开发中能实现类似MS Office那样实现VBA宏的 ...
- 玩转动态编译 - 高级篇:三,实例属性的读取与设置
实例属性的读取 先来回顾下静态属性读取的IL代码: .method public hidebysig instance string AAA() cil managed {.maxstack 8L_0 ...
- Java封装动态编译
最近根据公司的业务需要通过前端页面传过来字符串的代码,并且通过动态编译然后执行,支持的类型为 JS.Java字符串.class文件 的方式,由于实现的方式都各不相同,所以进行统一封装一下 1. 代码结 ...
- 网页制作表单代码java_JSP动态网页入门:表单输入例子
我们将创建一个web页面,它有一个输入表单,用户可以输入一个股票代号以获得出当前股票价格(有20分钟延迟).如果输入有误,则显示错误提示页面. quote.jsp 首先,用以下代码创建quote.js ...
- JavaCompiler实战:将Java源代码字符串动态编译成java类
.首先我们来认识一下 java中的一个对象 JavaCompiler JavaCompiler : 不知道肯定很陌生,其实这个api出来很久了,他是jdk6的特性,用来编译java的源程式的,详细介绍 ...
- NetCore基于Roslyn的动态编译实现
目录 一. AvalonEdit文本器 1.功能实现 2. 高亮 3. 代码提示 4. 动态编译 1)依赖项初始化 2) 编译函数 二. 运行效果展示 三. 源码链接 四. 参考资料 一. Avalo ...
最新文章
- 专家谈计算机体系架构研究获“图灵奖”
- 【安卓开发 】Android初级开发(网络操作)
- IO模型(epoll)--详解-02
- 情绪调节的自适应_如何做好情绪的管理者
- AWS Lambda中的Cron表达式解析器
- 吴恩达机器学习作业Python实现(二):logistic回归
- 练习-CSS3 多栏(Multi-column)
- C++判断一个序列是否为堆(最大堆、最小堆)
- BamlViewer修改
- fragment嵌套viewpager嵌套fragment第二次加载数据不显示问题
- VMware 12 专业版永久许可证密钥
- 机器码、序列号、认证码、注册码的生成算法(三)
- 计算机ps基础知识教案范文,平面设计基础教案范文
- Keil5下载及安装
- 轻量级录屏软件 Captura 使用 ffmpeg 调用 NVDIA nvenc 录制小体积网课视频
- 如何生成dll文件 采用VS2017生成dll文件(动态库文件)和lib文件(静态库文件)以C语言为例
- python制作的简单程序_Python如何制作简易收银小程序
- Java 1.4(打印表格)编写程序,显示以下表格。
- 基于GLFW的OpenGL学习001_艾孜尔江笔记
- 如何将图片存进SQL数据库中以及从数据库读取照片(解决办法)