1. 前言
前两天跟着享学的课学了一下热修复的原理
这里记录一下
2. 知识点
- 反射Reflect
- 类加载器ClassLoader
- Gradle开发
- dex文件打包
3. 原理
在Android中所有我们运行期间需要的类都是由ClassLoader(类加载器)进行加载。因此让ClassLoader加载全新的类替换掉出现Bug的类即可完成热修复。
所以我们需要作的工作
1. 将需要修复的.class文件打包成一个单独dex包
2.通过反射PathClassLoader将这个dex文件与原来的dex文件合并
具体来说
第一步:获取到当前应用的PathClassloader
第二步:反射获取到DexPathList属性对象pathList
第三步:反射修改pathList的dexElements
目的是将我们的补丁dex文件插入到dexElements
4. 代码实现
反射工具类
public class SharedReflectUtil {
/**
* 反射获取属性对象
* @param object 类对象
* @param fieldName 属性名称
* @return 属性对象
*/
public static Field getField(Object object, String fieldName) throws NoSuchFieldException {
for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {
System.out.println("getField.cls:" + cls.getName());
Field[] declaredFields = cls.getDeclaredFields();
System.out.println("fileds.size:" + declaredFields.length);
for (Field field : declaredFields) {
System.out.println(field.getName());
}
try {
Field declaredField = cls.getDeclaredField(fieldName);
//设置访问权限
declaredField.setAccessible(true);
return declaredField;
} catch (NoSuchFieldException e) {
//如果没找到,就要去父类找
}
}
throw new NoSuchFieldException( object.getClass().getSimpleName() + " No Such Filed:" + fieldName);
}
public static Method getMethod(Object object, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
for (Class<?> cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {
System.out.println("getMethod.cls:" + cls.getName());
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
try {
Method declaredMethod = cls.getDeclaredMethod(methodName, parameterTypes);
//设置访问权限
declaredMethod.setAccessible(true);
return declaredMethod;
} catch (NoSuchMethodException e) {
//如果没找到,就要去父类找
}
}
throw new NoSuchMethodException( object.getClass().getSimpleName() + " No Such Method:" + methodName);
}
}
补丁安装
public static void installPatch(Context context, String patchPath) {
File patchFile = new File(patchPath);
if (!patchFile.exists()) {
Log.e(TAG, "installPatch: " + patchPath + " is not exists");
return;
}
Log.d(TAG, "installPatch: " + patchPath);
File cacheFile = context.getCacheDir();
//PathClassLoader
ClassLoader classLoader = context.getClassLoader();
//获取pathList属性对象,这个对象存在于其父类 BaseDexClassLoader中
try {
Field pathListField = SharedReflectUtil.getField(classLoader, "pathList");
Log.i(TAG, "installPatch: find field:" + pathListField.getName() + "!!!");
//通过get得到pathList的示例对象Object DexPathList
Object pathList = pathListField.get(classLoader);
//获取DexPathList中的 dexElements的示例对象
Field dexElementsField = SharedReflectUtil.getField(pathList, "dexElements");
Log.i(TAG, "installPatch: find field:" + dexElementsField.getName() + "!!!");
Object[] dexElements = (Object[]) dexElementsField.get(pathList);
//执行DexPathList的makeDexElements,将我们的dex转换成Element[]
ArrayList<IOException> suppressedExceptions = new ArrayList<>();
File file = new File(patchPath);
ArrayList<File> files = new ArrayList<>();
files.add(file);
Method makeDexElementsMethod;
Object[] newElements;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//6.0以上
makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makePathElements",
List.class, File.class, List.class);
newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//4.4-6.0
makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
ArrayList.class, File.class, ArrayList.class);
newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile, suppressedExceptions);
//4.4 会有一个bug
//Class ref in pre-verified class resolved to unexpected implementation
} else {
//4.0-4.3
makeDexElementsMethod = SharedReflectUtil.getMethod(pathList, "makeDexElements",
ArrayList.class, File.class);
newElements = (Object[]) makeDexElementsMethod.invoke(null, files, cacheFile);
}
//创建一个新的数组,将旧的Element数组跟newElement数组进行合并
Object[] replaceDexElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(), dexElements.length + newElements.length);
System.arraycopy(newElements, 0, replaceDexElements, 0, newElements.length);
System.arraycopy(dexElements, 0, replaceDexElements, newElements.length, dexElements.length);
//替换属性值
dexElementsField.set(pathList, replaceDexElements);
Log.i(TAG, "installPatch: SUCCESS");
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
然后在Application中执行安装即可
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ClassLoader classLoader = getClassLoader();
System.out.println(classLoader);
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
HotFix.installPatch(this, "/sdcard/patch.jar");
}
}
最后如何生成这个补丁jar文件呢
修复好bug后,编译一次
在下面目录中,找到修复后的.class文件
然后利用sdk中的dx工具,将class文件打包成jar或者dex文件
命令: dx --dex --output=patch.jar MyUtils.class
然后拷贝到sdcard下就可以重启app开始修复了
但是这样打包补丁文件真的很蠢,万一一个app中有10几个文件需要修复
这样很没有效率
下篇文章将讲述如何使用Gradle自动化打包补丁