框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架
Posted 战国剑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架相关的知识,希望对你有一定的参考价值。
本文用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>
@Override
public 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 project
SunnyTransForm(project)
this.project = project
@Override
public String getName()
return "SunnyTransForm"
@Override
public Set<QualifiedContent.ContentType> getInputTypes()
return TransformManager.CONTENT_CLASS
@Override
public Set<QualifiedContent.Scope> getScopes()
return TransformManager.SCOPE_FULL_PROJECT
@Override
public boolean isIncremental()
return false;
@Override
void 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.absolutePath
pool.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.absolutePath
if (!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.MainActivity
def name = className.replace(SdkConstants.DOT_CLASS, "").substring(1)
println("name------------>:" + name)
//取到字节码操作对象。类似 json --> 转javabean
CtClass 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热修复框架的主要内容,如果未能解决你的问题,请参考以下文章