Android 代码热修复详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 代码热修复详解相关的知识,希望对你有一定的参考价值。
java:类加载原理:
当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程,具体的加载过程如下:
1、源 ClassLoader 先判断该 Class 是否已加载,如果已加载,则直接返回 Class,如果没有则委托给父类加载器。
2、父类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则委托给祖父类加载器。
3、依此类推,直到始祖类加载器(引用类加载器)。
4、始祖类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的子类加载器。
5、始祖类加载器的子类加载器尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的孙类加载器。
6、依此类推,直到源 ClassLoader。
7、源 ClassLoader 尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,源 ClassLoader 不会再委托其子类加载器,而是抛出异常
android类加载:
Android中的类加载器是BootClassLoader、PathClassLoader、DexClassLoader,其中BootClassLoader是虚拟机加载系统类需要用到的,PathClassLoader是App加载自身dex文件中的类用到的,DexClassLoader可以加载直接或间接包含dex文件的文件。
PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,它的一个DexPathList类型的成员变量pathList很重要。DexPathList中有一个Element类型的数组dexElements,这个数组中存放了包含dex文件(对应的是DexFile)的元素。BaseDexClassLoader加载一个类,最后调用的是DexFile的方法进行加载的
下面是代码热修复的两种方式:
1、根据类加载为父委托加载原理 将加载修复后的dex的DexClassLoader插入到PathClassLoader和BootstrapClassLoader中间,也就是将DexClassLoader设置为PathClassLoader的父加载器,将BootstrapClassLoader设置为DexClassLoader 顺序为:BootstrapClassLoader--->DexClassLoader--->PathClassLoader
//根据类加载为父委托加载原理 替换有bug的类放在dexPath中让DexClassLoader优先加载
//类加载顺序:BootstrapClassLoader---->DexClassLoader----->PathClassLoader
public void loadPatchDex(Context context, String dexPath, String optimizedDirectory, String librarySearchPath) {
ClassLoader currentClassLoader = context.getClassLoader();//context加载器(PathClassLoader)
ClassLoader parentClassLoader = currentClassLoader.getParent();//context的父加载器(BootstrapClassLoader)
//加载dexPath加载器
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parentClassLoader);
this.setField(ClassLoader.class, this.mParentFieldName, currentClassLoader, dexClassLoader);//设置当前类加载器的父加载器为dexClassLoader
}
/**
* @param clazz
* @param fieldName 属性名称
* @param target 要设置属性的对象
* @param value 设置属性的值
* @return 设置成功
*/
private boolean setField(Class clazz, String fieldName, Object target, Object value) {
try {
Field field = clazz.getDeclaredField(fieldName);
if (field != null) {
field.setAccessible(true);
field.set(target, value);
}
return true;
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;//设置失败
}
2、根据安卓的PathClassLoader加载机制将DexClassLoader加载的新的Dex添加的DexPathList的dexElements属性列表中,将更新的dex加载放在dexElements列表索引之前
public void loadPatchDex2(Context context, String dexPath, String optimizedDirectory, String librarySearchPath) {
ClassLoader pathClassLoader = context.getClassLoader();
//加载dexPath加载器
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, pathClassLoader.getParent());
//获取BaseDexClassLoader的DexPathList属性 私有的需要通过反射获取
Object dexPathList1 = this.getFieldValue(BaseDexClassLoader.class, this.mDexPathListFieldName, pathClassLoader);//pathClassLoader的PathList属性
Object dexPathList2 = this.getFieldValue(BaseDexClassLoader.class, this.mDexPathListFieldName, dexClassLoader);
if (dexPathList1 != null && dexPathList2 != null) {
//获取对应pathList中的dexElements属性
Object dexElements1 = this.getFieldValue(dexPathList1.getClass(), this.mElementFieldName, dexPathList1);//当前PathClassLoader中的Element[]属性
Object dexElements2 = this.getFieldValue(dexPathList2.getClass(), this.mElementFieldName, dexPathList2);//DexClassLoader中的Element[]属性
if (dexElements1 != null && dexElements2 != null) {
//将两个Element[]属性合并
Object finalElements = combineArray(dexElements2, dexElements1);
//将合并的值设置给当前的pathClassLoader的Element[]中
this.setField(dexPathList1.getClass(), this.mElementFieldName, dexPathList1, finalElements);
Log.e("HotFixEngine", "loadPatchDex2: success");
}
}
}
/**
* 获取对应属性值
* @param clazz
* @param fieldName 属性名称
* @param target 要操作的对象
* @return
*/
private Object getFieldValue(Class clazz, String fieldName, Object target) {
Field field = this.getField(clazz, fieldName);
if (field != null) {
try {
field.setAccessible(true);
return field.get(target);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
private Field getField(Class clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
/**
* 两个数组合并
*
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
Demo地址:https://github.com/xuguohongai/android/tree/master/AndroidHotFixDemo
以上是关于Android 代码热修复详解的主要内容,如果未能解决你的问题,请参考以下文章