安卓手写热修复demo
目录
用[TOC]`来生成目录:
预备知识
了解安卓如何加载类:Android插件化开发之动态加载基础之ClassLoader工作机制
demo:码云地址
在预备知识中可以了解到安卓初次加载类调用链如下
PathClassLoader.findClass->DexPathList.findClass->Element.loadClassBinaryName
并且当一个类加载到内存后,将不会再次加载。
本文的修复原理是修改DexPathList中的dexElements,让系统优先加载热修复包中的类达到修复功能
提示:本文只涉及原理与简单实现,并未考虑实际开发适配等等问题
实现步骤
1、获取系统dexElements
1.系统的dexElements在PathClassLoader中PathList的成员变量
2.在Context中 getClassLoader即可获取PathClassLoader。
3.代码如下
classLoader classLoader= getClassLoader();
Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");//通过反射获取ClassLoader中pathList成员变量Filed
Object pathList = pathListField.get(classLoader);
Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");//通过反射获取DexPathList中dexElements成员变量Filed
Object dexElements = dexElementsFiled.get(pathList);
ReflectUtil为反射工具类
2、加载外部Dex文件
1.加载外部dex文件此处用,DexClassLoader
DexClassLoader dexClassLoader = new DexClassLoader(filePath,
optimizedDirectory.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
此处重要的参数为前两个
第一个为dex文件路径,第二个必须为一个可读写的文件夹路径。其他的在预备知识有了解不多赘述。
在实例化DexClassLoader后,当前DexClassLoader中dexElements将会被赋值。
再次类似通过获取系统dexElements一样获取dexClassLoader中dexElements成员。
3、将外部dex文件中的dexElements与系统dexElements合并
现在获取到了系统的dexElements为 pathElements,外部加载dexElements为outElements
由于系统加载类会按dexElement的数组顺序查找类,在合并时需要将outElements放在pathElemnets前面才能优先被系统加载
Object arrayAppend = arrayAppend(outElements, pathElemnets);//将outElements, pathElemnets合并成一个数组
Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");
Object pathList = pathListField.get(classLoader);
Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");
dexElementsFiled.set(pathList, arrayAppend);//将系统dexElements修改为合并后的数组即可
此修复方法在类已经加载过不能达到修复效果,在修复需要在application中修复。
修复效果测试
定义测试
1、新建一个类
public class Function {
public void test(){
throw new RuntimeException("sss");
}
}
2、在MainActivity中设置一个按钮的点击监听为如下
public void click(View view) {
Function function = new Function();
function.test();
}
建立热修复dex文件
在测试时,用Function.class生成dex文件总是报如下错:
PARSE ERROR:
class name (com/spyfool/hotfixtdemo/Function) does not match path (C:/Users/Admi
nistrator/Desktop/dex/com/spyfool/hotfixtdemo/Function.class)
…while parsing C:/Users/Administrator/Desktop/dex/com/spyfool/hotfixtdemo/Func
tion.class。具体原因未知,望有人能解答一下
新建一个Library包,包名跟appv包名相同。新建Function类
public class Function {
public void test(){
//此处只要表现跟app中Function表现不一致即可用于验证修复是否成功
}
}
目录:工程目录\mylibrary(library目录)\build\outputs\aar\mylibrary-debug.aar。将此aar按后缀改成zip,并解压可以得到jar文件,将此jar文件转成dex文件即可用于修复。如果没有aar文件,rebuild project即可
转换命令: dx –dex –output [生成dex文件绝对路径 例:D:/d.dex] [jar文件绝对路径]
其中 dx命令需要配置变量。或者使用dx文件绝对路径也可 例: E:\sdk\build-tools\26.0.3\dx.bat –dex –output E:\d.dex E:\classes.jar
以上内容如有问题请指正。