文章主要内容为:
1.android的dex加载流程
2.利用DexClassLoader手动实现一个简单版的热修复
- 首先我们来了解一下什么是dex
在android虚拟机里面是无法直接运行.class文件,android会将所有的.class文件转换成一个.dex文件,然后通过DexClassLoader来加载.dex - dexElements数组是什么
是所有dex文件在系统内存中的表现形式 - DexClassLoader实现热修复的方式
从服务器端下载下来你的修复好的dex文件,利用代码在dexElements数组中将修复好的dex文件插到有bug的dex文件前面 - DexClassLoader实现热修复的原理
首先将dex文件移动到odex的缓存目录(data/user/包名/app_odex),然后通过反射BaseDexClassLoader,找到BaseDexClassLoader下的pathList,pathList下面有个dexElements数组;分别将系统的dexElements数组和dex文件的dexElements数组创建出来,然后将dex的dexElements数组插入到系统的dexElements数组前面,最后将新的dexElements数组赋值到系统的dexElements中
好的,我们接下来用代码来实现热修复核心代码
- 首先将手机储存路径下的dex文件移动到odex缓存目录下
private void fix() {
File filesDir = this.getDir("odex", Context.MODE_PRIVATE);
String name = "out.dex";
String filePath = new File(filesDir, name).getAbsolutePath();
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//这里是修复dex的工具类,下面会写到
FixUtils.loadDex(this);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 现在开始写dex融合的工具类
public class FixUtils {
private static HashSet<File> loadDexList = new HashSet<File>();
public static void loadDex(Context context) {
if (context == null) {
return;
}
//将odex文件夹下的所有.dex文件存放到loadDexList下
File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
File[] listFiles=filesDir.listFiles();
for (File file : listFiles) {
if(file.getName().startsWith("classes")||file.getName().endsWith(".dex")){
loadDexList.add(file);
}
}
//创建一个odex的缓存目录
String optimizeDir = filesDir.getAbsolutePath() + File.separator + "cache_dex";
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
//遍历list下所有的.dex文件 将.dex文件插入到系统的dexElements前面
for (File dex : loadDexList) {
DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), fopt.getAbsolutePath(), null, context.getClassLoader());
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
try {
//利用反射创建系统的ClassLoader
Class baseDexClazzLoader=Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListFiled=baseDexClazzLoader.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathListObject = pathListFiled.get(pathClassLoader);
Class systemPathClazz=pathListObject.getClass();
Field systemElementsField = systemPathClazz.getDeclaredField("dexElements");
systemElementsField.setAccessible(true);
Object systemElements=systemElementsField.get(pathListObject);
//利用反射创建自己的ClassLoader
Class myDexClazzLoader=Class.forName("dalvik.system.BaseDexClassLoader");
Field myPathListFiled=myDexClazzLoader.getDeclaredField("pathList");
myPathListFiled.setAccessible(true);
Object myPathListObject =myPathListFiled.get(classLoader);
Class myPathClazz=myPathListObject.getClass();
Field myElementsField = myPathClazz.getDeclaredField("dexElements");
myElementsField.setAccessible(true);
Object myElements=myElementsField.get(myPathListObject);
//将自己的ClassLoader的dexElements插入到系统的dexElements前面
Class<?> sigleElementClazz = systemElements.getClass().getComponentType();
int systemLength = Array.getLength(systemElements);
int myLength = Array.getLength(myElements);
int newSystenLength = systemLength + myLength;
//新建一个空的dexElements,长度为系统的dexElements长度+自己.dex文件的dexElements长度
Object newElementsArray = Array.newInstance(sigleElementClazz, newSystenLength);
//先将自己的.dex文件的dexElements插入到上面新建的dexElements里面,然后再将系统的dexElements插入进来
//这样就实现了先加载修复好的dex的,然后在加载系统的dex
for (int i = 0; i < newSystenLength; i++) {
if (i < myLength) {
Array.set(newElementsArray, i, Array.get(myElements, i));
}else {
Array.set(newElementsArray, i, Array.get(systemElements, i - myLength));
}
}
//插入完成,将上面新建的dexElements赋值给系统的dexElements
Field elementsField=pathListObject.getClass().getDeclaredField("dexElements");
elementsField.setAccessible(true);
elementsField.set(pathListObject,newElementsArray);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 修复代码已经写完了,我们可以先创建一个修复前的测试类
public class Test {
public void testFix(Context context){
Toast.makeText(context, "修复前", Toast.LENGTH_SHORT).show();
}
}
然后运行到手机上
- 在创建一个修复之后的测试类
public class Test {
public void testFix(Context context){
Toast.makeText(context, "修复后", Toast.LENGTH_SHORT).show();
}
}
- 现在需要将修复后的test.java文件编译成.dex,在android sdk的tool下,有一个dx工具可以使用,将修复好的Test.java拿出来,使用dx工具编译成out.dex,然后将out.dex放到手机储存卡下面,第一次进入的时候,先运行上面写的fix()方法,就能够成功实现热修复了
网友评论