本文用javassist方式,模拟美团Robust插件的前置处理:用插入代码的方式,针对apk中的每个方法都插入一段静态代码判断语句,用于控制是否启用热修复fix(也就是动态加载patch包到原apk中)。

一、插件的生成与依赖导入

1、用该文章中的方法:Android中Plugin插件工程的自动生成 自动生成一个android的插件模板;

2、加入javassist依赖,加入依赖后的插件build的格式如下:

dependencies{compile gradleApi()compile localGroovy()//transform与javassist依赖增加compile 'com.android.tools.build:transform-api:1.5.0'compile 'javassist:javassist:3.12.1.GA'compile 'commons-io:commons-io:2.5'implementation 'com.android.tools.build:gradle:3.6.3'}

二、javassist编译时处理代码

javassist的作用期是编译打包时从.class--转换-->.dex过程中。本例中,将通过插件的方式,用字节码手术刀javassist,修改class字节码文件,为各方法添加代码。

注:javassist、aspectJ、apt三种编译时技术的起效时期如下:

1、插件注册

在生成的插件模板中,做编辑。新增一个transForm到编译过程。

public class SunnyEasyUse implements Plugin<Project> {@Overridepublic void apply(Project project) {println 'A Plugin'//该插件配置到对应工程的build中,此处做注册def android = project.extensions.getByType(AppExtension)//将SunnyTransForm加入到编译过程android.registerTransform(new SunnyTransForm(project))println 'register A Plugin'}
}

2、TransFrom编写

此处主要做两个操作:

(1)将第三方依赖(jar包),从上一个transForm中取出,并传递到下一个transForm;

(2)对源文件(也就是自己编写的代码)做处理,在代码的方法中插入预期代码;

1、热修复控制类新建

首先,在主工程中,新建类如下:

public class FixUtil {//是否开启使用热修复public static boolean isFixUse(){return false;}
}

2、TransFrom针对所有方法,将上述判断插入

具体步骤,参照注释

public class SunnyTransForm extends Transform {//字节码池def pool = ClassPool.default//工程def projectSunnyTransForm(project) {this.project = project}@Overridepublic String getName() {return "SunnyTransForm"}@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return TransformManager.CONTENT_CLASS}@Overridepublic Set<QualifiedContent.Scope> getScopes() {return TransformManager.SCOPE_FULL_PROJECT}@Overridepublic boolean isIncremental() {return false;}@Overridevoid transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation)//class路径,加载到字节码池。//把project下的每个class路径,加载到pool字节码池,也就是加载到内存中project.android.bootClasspath.each {pool.appendClassPath(it.absolutePath)}//输出def outProvider = transformInvocation.getOutputProvider()//输入的文件处理transformInvocation.inputs.each { input ->//jar包处理input.jarInputs.each { it ->processJarInput(it, outProvider)}//源文件---自己写的源文件,都放置在文件夹中input.directoryInputs.each { it ->processDirectoryInput(it, outProvider)}}}private void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {def destFile = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)pool.insertClassPath(jarInput.file.absolutePath)FileUtils.copyFile(jarInput.file, destFile)}private void processDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {def preFileName = directoryInput.file.absolutePathpool.insertClassPath(preFileName)//开始修改class代码findTargetClassFile(directoryInput.file, preFileName)def destFile = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)FileUtils.copyDirectory(directoryInput.file, destFile)}private void findTargetClassFile(File file, String fileName) {//递归查找.class结尾的文件if (file.isDirectory()) {file.listFiles().each {findTargetClassFile(it, fileName)}} else {modify(file, fileName)}}//修改代码private void modify(File file, String fileName) {def filePath = file.absolutePathif (!filePath.endsWith(SdkConstants.DOT_CLASS)) {return}if (filePath.contains('R$') || filePath.contains('R.class') || filePath.contains('BuildConfig.class')) {return}println("文件路径------------>:" + fileName)//根据路径得到包名与类名,用于加载//fileName是文件夹// 正反斜杠是兼容window与mac// 获取到 类似com.sunny.file.MainActivity.class全类名def className = filePath.replace(fileName, "").replace("\\", ".").replace("/", ".")//获取 类似com.sunny.file.MainActivitydef name = className.replace(SdkConstants.DOT_CLASS, "").substring(1)println("name------------>:" + name)//取到字节码操作对象。类似 json --> 转javabeanCtClass ctClass = pool.get(name)if (name.contains("com.sunny.aplugin")) {def modifyBody = "if(com.sunny.aplugin.FixUtil.isFixUse()){}"addCode(ctClass, modifyBody,fileName)}}//添加代码到方法中private void addCode(CtClass ctClass, String body,String fileName) {println("addCode------------>:" + body)CtMethod[] methods = ctClass.getDeclaredMethods()ctClass.defrost()methods.each {if(!it.getName().contains("isFixUse")){println("it.getName()------------>:" + it.getName())//插入到方法最前it.insertBefore(body)}}ctClass.writeFile(fileName)ctClass.detach()}
}

3、编译后效果

(1)在编译的build目录下,可以看到新的transForm

(2)被修改后的方法,如类MainActivity中的onCreate中,在方法头部位置,插入了判断代码

三、热修复的后续操作说明

至此,热修复Robust前期的核心工作已经做完。

后续需要做的是将下载到本地的patch文件,通过动态加载的方式,加入到apk中。

此外,javessist除以上可以插入到方法头部,也可以如aspectJ一样,在方法前、方法后、环绕等方式处理方法。

框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架相关推荐

  1. java修改字节码技术,Javassist修改class,ASM修改class

    背景: 项目使用的Logback 1.1.11版本的类ch.qos.logback.core.rolling.helper.RollingCalendar的periodBarriersCrossed方 ...

  2. 从头开始实现一个小型spring框架——手写Spring之集成Tomcat服务器

    手写Spring之集成Tomcat与Servlet 写在前面 一.Web服务模型及servlet 1.1 Web服务器 1.2 请求流程 二.实现 三.小结 写在前面 最近学习了一下spring的相关 ...

  3. 【Android 热修复】热修复原理 ( 热修复框架简介 | 将 Java 字节码文件打包到 Dex 文件 )

    文章目录 一. 热修复框架简介 1.类替换 2.so 替换 3.资源替换 4.全平台支持 5.生效时间 6.性能损耗 7.总结 二. 将 Java 字节码文件打包到 Dex 文件 一. 热修复框架简介 ...

  4. Android之热修复框架Nuwa

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/70284239 本文出自:[顾林海的博客] ##前言 当热修复框架还没出 ...

  5. 【手写系列】写出我的第一个框架:迷你版Spring MVC

    你没有看错标题,今天,我将实现我人生中第一个框架,^_^ 前期准备 我这里要写的是一个迷你版的Spring MVC,我将在一个干净的web工程开始开发,不引入Spring,完全通过JDK来实现. 我们 ...

  6. 【手写系列】纯手写实现JDK动态代理

    前言 在Java领域,动态代理应用非常广泛,特别是流行的Spring/MyBatis等框架.JDK本身是有实现动态代理技术的,不过要求被代理的类必须实现接口,不过cglib对这一不足进行了有效补充.本 ...

  7. 【手写系列】透彻理解MyBatis设计思想之手写实现

    前言 MyBatis,曾经给我的感觉是一个很神奇的东西,我们只需要按照规范写好XXXMapper.xml以及XXXMapper.java接口.要知道我们并没有提供XXXMapper.java的实现类, ...

  8. 一起手写Vue3核心模块源码,掌握阅读源码的正确方法

    最近和一个猎头聊天,说到现在前端供需脱节的境况.一方面用人方招不到想要的中高级前端,另一方面市场上有大量初级前端薪资要不上价. 特别是用 Vue 框架的,因为好上手,所以很多人将 Vue 作为入门框架 ...

  9. Android 进阶之路:ASM 修改字节码,这样学就对了!

    本文已授权个人公众号「鸿洋」原创发布. 恢复双休了,准备捡起来写博客这件事,会尝试写好每一篇博客,准备写一个「进阶之路」的系列,希望对你有用. 没错,看了很多 ASM 入门的文章,都感觉文章写的很轻松 ...

最新文章

  1. 好想自己做个迷宫呀!
  2. Momenta获C轮5亿美元融资,上汽、丰田、博世等领投 | 九合系融资新闻
  3. OTA整包的制作流程(未完)
  4. Linux Shell常用技巧(十二)
  5. NGINX原理 之 SLAB分配机制(转)
  6. 【java】两个线程如何交替执行,一个输出偶数一个输出奇数?
  7. 一点一滴岗位测试答案_心理测试:凭直觉,选你最喜欢的一件睡衣,测你的野心是什么级别...
  8. Python学习笔记(2) Python提取《釜山行》人物关系
  9. CMOS工作原理和概念
  10. 一周信创舆情观察(12.7~12.13)
  11. 《SVN宇宙版教程》:第七章 Subclipse更新与深度
  12. Instead Of 触发器
  13. 环境和社会风险分类c类_风险分类
  14. 一个程序员自媒体人的2017年终总结
  15. 模拟退火MATLAB
  16. 搭建一个vue小页面(入门vue)
  17. 计算机专业英语中tour的意思,计算机专业英语自我介绍
  18. python数据分析比较好的书籍_python数据分析比较好的书籍推荐|陇川制作项目盈利能力分析...
  19. Wi-Fi 6关键技术及产业进展
  20. 奇瑞新能源又一款新车上市 奇瑞无界Pro炫酷来袭

热门文章

  1. Random类Dome01 nextInt()方法
  2. 神经辐射场 (NeRF) 概念
  3. 骨传导耳机靠什么发声的?骨传导耳机值得入手吗?
  4. 西安比较好的java软件公司_西安java软件培训,西安java软件找工作难吗,西安java软件培训前十名有哪些...
  5. 计算机科技有限公司祝贺词,祝福科技工作好词好句子37条
  6. 计算机义诊暑期社会实践报告,义诊 暑期社会实践报告
  7. webstorm安装介绍
  8. 编写测试用例方法之正交表分析法
  9. 酷睿i5 12450h怎么样 i512450h参数相当于什么水平
  10. 使用Windows本机库扩展用于ESSO AccessProfile的IBM Security Access Manager