Android进阶从字节码插桩技术了解美团热修复实例详解
目录
- 引言
- 1 插件发布
- 2 Javassist
- 2.1 准备工作
- 2.2 Transform
- 2.3 transform函数注入代码
- 2.3.1 Jar包处理
- 2.3.2 字节码处理
- 2.4 Javassist织入代码
- 2.4.1 ClassPool
- 2.4.2 CtClass
引言
热修复技术如今已经不是一个新颖的技术,很多公司都在用,而且像阿里、腾讯等互联网巨头都有自己的热修复框架,像阿里的AndFix采用的是hook native底层修改代码指令集的方式;腾讯的Tinker采用类加载的方式修改dexElement;而美团则是采用字节码插桩的方式,也就是本文将介绍的一种技术手段。
我们知道,如果上线出现bug,通常是发生在方法的调用阶段,某个方法异常导致崩溃;字节码插桩,就是在编译阶段将一段代码插入该方法中,如果线上崩溃,需要发布补丁包,同时在执行该方法时,如果检测到补丁包的存在,将会走插桩插入的逻辑,而不是原逻辑。
如果想要知道美团实现的热修复框架原理,那么首先需要知道,robust该怎么用
对于每个模块,如果想要插桩需要引入robust插件,所以如果自己实现一个简单的robust的功能,就需要创建一个插件,然后在插件中处理逻辑,我个人喜欢在buildSrc里写插件然后发布,当然也可以自己创建一个java工程改造成groovy工程
plugins { id 'groovy' id 'maven-publish' } dependencies { implementation gradleApi() implementation localGroovy() implementation 'com.android.tools.build:gradle:3.1.2' }
如果创建一个java模块,如果要【改装】成一个groovy工程,就需要做上述的配置
1 插件发布
初始化之后,我一般会先建2个文件夹
plugin用于自定义插件,定义输入输出; task用于任务执行。
class MyRobustPlugin implements Plugin<Project>{ @Override void apply(Project project) { //项目配置阶段执行,配置完成之后, project.afterEvaluate { println '插件开始执行了' } } }
如果需要发布插件到maven仓库,或者放在本地,可以通过maven-publish(gradle 7.0+)插件来实现
afterEvaluate { publishing { publications{ releaseType(MavenPublication){ from components.java groupId 'com.demo' artifactId 'robust' version '0.0.1' } } repositories { maven { url uri('../repo') } } } }
publications:这里可以添加你要发布的maven版本配置 repositories:maven仓库的地址,这里就是写在本地一个文件夹
重新编译之后,在publish文件夹下会生成很多任务,执行发布到maven仓库的任务,就会在本地的repo文件夹下生成对应的jar包
接下来我们尝试用下这个插件
buildscript { repositories { google() mavenCentral() jcenter() //这里配置了我们的插件依赖的本地仓库地址 maven { url uri('repo') } } dependencies { classpath "com.android.tools.build:gradle:7.0.3" classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10" classpath "com.demo:robust:0.0.1" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
配置完成后,在app模块添加插件依赖
apply plugin:'com.demo'
这里会报错,com.demo这个插件id找不到,原因就是,其实插件是一个jar包,然后我们只是创建了这个插件,并没有声明入口,在编译jar包时找不到清单文件,因此需要在资源文件夹下声明清单文件
implementation-class=com.tal.robust.plugin.MyRobustPlugin
创建插件名字的属性文件,声明插件的入口,就是我们自己定义的插件,再次编译运行
这也意味着,我们的插件执行成功了,所以准备工作已完成,如果需要插桩的模块,那么就需要依赖这个插件
2 Javassist
Javassist号称字节码手术刀,能够在class文件生成之后,打包成dex文件之前就将我们自定义的代码插入某个位置,例如在getClassId方法第62行代码的位置,插入逻辑判断代码
2.1 准备工作
引入Javassist,插件工程引入Javassist
implementation 'org.javassist:javassist:3.20.0-GA'
2.2 Transform
Javassist作用于class文件生成之后,在dex文件生成之前,所以如果想要对字节码做处理,就需要在这个阶段执行代码插入,这里就涉及到了一个概念 --- transform;
Android官方对于transform做出的定义就是:Transform用于在class打包成dex这个中间过程,对字节码做修改
在build文件夹中,我们可以看到这些文件夹,像merged_assets、merged_java_res等,这是Gradle的Transform,用于打包资源文件到apk文件中,执行的顺序为串行执行,一个任务的输出为下一个任务的输入,而在transforms文件夹下就是我们自己定义的transform
implementation 'com.android.tools.build:transform-api:1.5.0'
导入Transform依赖