一.什么是热修复:
正常开发流程

热修复开发流程

热修复优势

Andfix的原理是,抽象类加载器classLoader的子类BaseDexClassLoader初始化pathList,pathList这个对象中存储着含.dex的dexElements数组,线上出现问题,提供将修复的类包装成.dex文件加入dexElements数组中前列,通过反射合并插桩pathList,最终修改了pathList中的dexElements数组对象。
下面演示编写bug,在线修复。
点击按钮 :10/0
public class ParamsSort {
public void math(Context context) {
int a = 10;
int b = 1;
Toast.makeText(context, "math >>> " + a / b, Toast.LENGTH_SHORT).show();
}
}
1.通过服务端接口下载dex文件,拷贝到私有目录临时文件夹 odex
// classes2.dex ---> /storage/emulated/0/classes2.dex
private void fixBug() {
// 通过服务器接口下载dex文件,v1.3.3版本有某一个热修复dex包
File sourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME);
// 目标路径:私有目录里的临时文件夹odex
File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath()
+ File.separator + Constants.DEX_NAME);
// 如果存在,比如之前修复过classes2.dex。清理
if (targetFile.exists()) {
targetFile.delete();
Toast.makeText(this, "删除已存在的dex文件", Toast.LENGTH_SHORT).show();
}
try {
// 复制修复包dex文件到app私有目录
FileUitls.copyFile(sourceFile, targetFile);
Toast.makeText(this, "复制dex文件完成", Toast.LENGTH_SHORT).show();
// 加载热修复Dex文件
FixDexUtils.loadFixedDex(this);
} catch (IOException e) {
e.printStackTrace();
}
}
2.拷贝完成后将需要修复的dex集合存入,主包情况不加入插桩队列:
/**
* 加载热修复的dex文件
* @param context 上下文
*/
public static void loadFixedDex(Context context) {
if (context == null) return;
// Dex文件目录(私有目录中,存在之前已经复制过来的修复包)
File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
// 遍历私有目录中所有的文件
for (File file : listFiles) {
// 找到修复包,加入到集合
if (file.getName().endsWith(Constants.DEX_SUFFIX) && !"classes.dex".equals(file.getName())) {
loadedDex.add(file);
}
}
// 模拟类加载器
createDexClassLoader(context, fileDir);
}
3.创建加载补丁的DexClassLoader
private static void createDexClassLoader(Context context, File fileDir) {
// 创建临时的解压目录(先解压到该目录,再加载java)
String optimizedDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
// 不存在就创建
File fopt = new File(optimizedDir);
if (!fopt.exists()) {
// 创建多级目录
fopt.mkdirs();
}
for (File dex : loadedDex) {
// 每遍历一个要修复的dex文件,就需要插桩一次
DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(),
optimizedDir, null, context.getClassLoader());
hotfix(classLoader, context);
}
}
4.热修复:
/**
* 热修复
* @param classLoader 自有的类加载器,加载了修复包的DexClassLoader
* @param context 上下文
*/
private static void hotfix(DexClassLoader classLoader, Context context) {
// 获取系统PathClassLoader类加载器
PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();
try {
// 获取自有的dexElements数组对象
Object myDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));
// 获取系统的dexElements数组对象
Object systemDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathLoader));
// 合并成新的dexElements数组对象
Object dexElements = ArrayUtils.combineArray(myDexElements, systemDexElements);
// 通过反射再去获取 系统的pathList对象
Object systemPathList = ReflectUtils.getPathList(pathLoader);
// 重新赋值给系统的pathList属性 --- 修改了pathList中的dexElements数组对象
ReflectUtils.setField(systemPathList, systemPathList.getClass(), dexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
上面通过反射获取系统dexElements 和修复的dexElements然后合并数组,再通过反射获取pathList 去重新付值dexElements
public class ReflectUtils {
/**
* 通过反射获取某对象,并设置私有可访问
*
* @param obj 该属性所属类的对象
* @param clazz 该属性所属类
* @param field 属性名
* @return 该属性对象
*/
private static Object getField(Object obj, Class<?> clazz, String field)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
Field localField = clazz.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
/**
* 给某属性赋值,并设置私有可访问
*
* @param obj 该属性所属类的对象
* @param clazz 该属性所属类
* @param value 值
*/
public static void setField(Object obj, Class<?> clazz, Object value)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
Field localField = clazz.getDeclaredField("dexElements");
localField.setAccessible(true);
localField.set(obj, value);
}
/**
* 通过反射获取BaseDexClassLoader对象中的PathList对象
*
* @param baseDexClassLoader BaseDexClassLoader对象
* @return PathList对象
*/
public static Object getPathList(Object baseDexClassLoader)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
/**
* 通过反射获取BaseDexClassLoader对象中的PathList对象,再获取dexElements对象
*
* @param paramObject PathList对象
* @return dexElements对象
*/
public static Object getDexElements(Object paramObject)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
return getField(paramObject, paramObject.getClass(), "dexElements");
}
}
网友评论