HotFix分析
引
这篇文章发出的时候,写HotFix的人已经看不到了。
大体步骤
HotFix的目的是在不重新安装apk的情况下做到代码的局部修改。
HotFix的大体执行步骤是这样的:
- 制作一个「空」的apk,里面只包含要修复的代码所在的类;
- 类中的要修改的方法打上注解;
- 在应用程序入口的onCreate方法里,检查网络上有没有patch;
- 检查完毕,调用
PatchManager
的fix
方法开始替换打了注解的代码。
具体执行顺序
fix
的执行步骤:
判断本地是否有补丁包,有就删掉,没有就加载
加载Dex用的是
DexFile.loadDex
方法。这个方法第一个参数是Jar or APK file with "classes.dex"
,包含classes.dex的jar或者apk;第二个参数是File that will hold the optimized form of the DEX data
,优化后的dex文件;返回一个新的或者之前打开的DexFile。然后新建一个新的classLoader,名为patchClassLoader,这个classloader使用当前
context
的classloader作为它的parent(双亲)
(这样就把新加载的class跟当前的context关联起来了,关联在同一棵树上),并且覆写这个双亲的findClass
方法。读过前面ClassLoader分析的朋友还记得吗,原本的findClass
方法是在DexPathList.java
中实现的,它遍历所有加载过的dex文件,用了native方法adClassBinaryName
一个个试,看能不能加载到想要的类,返回找到的Class
。覆写之后findClass的实现是这样的:
//the new overrided findClass() funciton
//patchDexFile是第二步中loadDex方法加载到的补丁包的DexFile
Class<?> clazz = patchDexFile.loadClass(className, this);
if (clazz == null
&& className.startsWith("com.wangyin.payment")) {
return Class.forName(className);
}
patchDexFile
是第2步中loadDex方法加载到的补丁包的DexFile,如果类名以com.wangyin.payment
开头,那就返回这个Class。
- 利用
patchDexFile.entries
返回Enumeration<String>
枚举类型的值,里面包含了patch中所有的class。然后,用第3步中新建的那个修改过双亲的findClass方法的classLoader来依次load patch中所有的class,step3中我们把findClass指定的寻找特征改成了com.wangyin.payment
开头的类。 - 反射调用step4中找到的要修改的类中的
run
方法。(这个run方法干了什么,由于我没有补丁apk,所以也不太清楚。。)
//run方法是Patch接口里的空方法,参数是patchPath,补丁apk的位置
patchClazz.getMethod("run", String.class).invoke(
patchClazz.newInstance(), patchPath);
- 执行
fixClass
方法替换要修复的类。fixClass()利用注解,找到所有打了注解的方法,然后用fixMethod
替换指定方法。
//fixClass
for (Method patchMethod : patchMethods) {
isFixed = patchMethod.getAnnotation(Fixed.class);
if (isFixed != null && isFixed.value()) {
fixMethod(mainClassLoader, patchMethod);
}
}
这个fixMethod
最终调用so中的native方法replaceMethod
来修复。
fixClass(patchClazz, mContext.getClassLoader());
总结
主要还是对classLoader的理解。最终的修复用的是native方法。
Jan 3rd