概述

现在市面上的插件化框架,热修复框架几乎都使用了动态编译技术。

动态编译的实质是,使用gradle transform api,在项目构建过程的class文件转成dex文件之前,通过自定义插件,进行class字节码处理。

本文主要是通过走一遍简单Demo实现流程,让读者能对动态编译有一个大概的了解。
如对一些细节知识有更多需求的读者就需要自行学习了。

简单Demo

本文的Demo,通过动态编译实现在代码中插入一行代码。
主要实现步骤如下:

  1. 实现gradle Plugin。
  2. 实现Transform,并且在Plugin中注册。
  3. Plugin编译,并且上传到本地仓库。
  4. app项目应用Plugin,通过插件实现动态编译。
  5. 运行项目,查看动态编译结果。

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模块

这个模块主要实现了两个内容:

  1. 实现插件
  2. 实现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模块

这个模块主要实现两个内容

  1. 应用插件
  2. 实现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)相关推荐

  1. 【java】深入理解Java的动态编译

    文章目录 1.概述 2. 前提 3. 基本原理# 4. JDK动态编译 4.1 实现JavaFileObject 4.2 实现ClassLoader 4.3 实现JavaFileManager 4.4 ...

  2. 注解、反射、动态编译、字节码操作

    注解.反射.动态编译.字节码操作 前言:本篇博客将介绍Java中注解的定义.使用以及反射对Java动态性的支持和Java字节码操作,通过本篇内容,读者将对Java知识有更加深刻的理解,同时为后面And ...

  3. 零基础AJAX入门(含Demo演示源文件)

    零基础AJAX入门(含Demo演示源文件) 作者:一点一滴的Beer  个人主页:http://www.cnblogs.com/beer 摘要:因为笔者的大四毕业设计是做WebGIS系统,用过Web版 ...

  4. C#发现之旅第十四讲 基于动态编译的VB.NET脚本引擎

    本章说明 在前面章节中,笔者使用了反射和动态编译技术实现了快速ORM框架,在本章中笔者将继续使用这些技术来实现一个VB.NET的脚本引擎,使得人们在开发中能实现类似MS Office那样实现VBA宏的 ...

  5. 玩转动态编译 - 高级篇:三,实例属性的读取与设置

    实例属性的读取 先来回顾下静态属性读取的IL代码: .method public hidebysig instance string AAA() cil managed {.maxstack 8L_0 ...

  6. Java封装动态编译

    最近根据公司的业务需要通过前端页面传过来字符串的代码,并且通过动态编译然后执行,支持的类型为 JS.Java字符串.class文件 的方式,由于实现的方式都各不相同,所以进行统一封装一下 1. 代码结 ...

  7. 网页制作表单代码java_JSP动态网页入门:表单输入例子

    我们将创建一个web页面,它有一个输入表单,用户可以输入一个股票代号以获得出当前股票价格(有20分钟延迟).如果输入有误,则显示错误提示页面. quote.jsp 首先,用以下代码创建quote.js ...

  8. JavaCompiler实战:将Java源代码字符串动态编译成java类

    .首先我们来认识一下 java中的一个对象 JavaCompiler JavaCompiler : 不知道肯定很陌生,其实这个api出来很久了,他是jdk6的特性,用来编译java的源程式的,详细介绍 ...

  9. NetCore基于Roslyn的动态编译实现

    目录 一. AvalonEdit文本器 1.功能实现 2. 高亮 3. 代码提示 4. 动态编译 1)依赖项初始化 2) 编译函数 二. 运行效果展示 三. 源码链接 四. 参考资料 一. Avalo ...

最新文章

  1. 专家谈计算机体系架构研究获“图灵奖”
  2. 【安卓开发 】Android初级开发(网络操作)
  3. IO模型(epoll)--详解-02
  4. 情绪调节的自适应_如何做好情绪的管理者
  5. AWS Lambda中的Cron表达式解析器
  6. 吴恩达机器学习作业Python实现(二):logistic回归
  7. 练习-CSS3 多栏(Multi-column)
  8. C++判断一个序列是否为堆(最大堆、最小堆)
  9. BamlViewer修改
  10. fragment嵌套viewpager嵌套fragment第二次加载数据不显示问题
  11. VMware 12 专业版永久许可证密钥
  12. 机器码、序列号、认证码、注册码的生成算法(三)
  13. 计算机ps基础知识教案范文,平面设计基础教案范文
  14. Keil5下载及安装
  15. 轻量级录屏软件 Captura 使用 ffmpeg 调用 NVDIA nvenc 录制小体积网课视频
  16. 如何生成dll文件 采用VS2017生成dll文件(动态库文件)和lib文件(静态库文件)以C语言为例
  17. python制作的简单程序_Python如何制作简易收银小程序
  18. Java 1.4(打印表格)编写程序,显示以下表格。
  19. 基于GLFW的OpenGL学习001_艾孜尔江笔记
  20. 如何将图片存进SQL数据库中以及从数据库读取照片(解决办法)

热门文章

  1. 盗版软件繁荣了IT业 害死了软件业
  2. 开启我的Python之旅
  3. 浅谈模式 - 桥梁模式
  4. visual studio 2017如何打开 *.sdf 数据库文件 (笔记)
  5. 解决 Error type 3 问题
  6. 一道有意思的js面试题
  7. asp空间如何判断jmail组件已经安装?是否支持呢?
  8. Python 实例 - Day3 - Spirograph 万花尺(完结)
  9. 关于ajax中的onreadystatechange
  10. win10磁盘管理D盘无法选择拓展卷