Android热修复原理-类加载机制与反射
Android热修复原理-类加载机制与反射
1,热修复⽅案:
(1) 阿⾥的AndFix 补丁⽅案,通过natvie层hook住带有bug的⽅法从⽽替换Java层的代码 。四五年没有维护了,应该是被弃⽤了,因为是通过C++代码来完成类的替换。实际就是反射拿到旧的类class,然后再把补丁包⾥⾯的class进⾏⼀个赋值,例如 old.class  =
new.class. 是可以⽴即⽣效的。
(2) 美团的Robust 补丁⽅案 ,就是在编译打包的阶段对每个函数都加⼊修复的代码。有点类似代理,将⽅法执⾏的代码重定向到修复了的⽅法中去。(运⽤了字节码插桩技术)  是可以⽴即⽣效的。
(3) 腾讯的Tinker, 通过对⽐指定的base APK版本 和修复好了之后的 APK包中dex⽂件 , 补丁包就是两者的差分⽂件,patch.dex  , 再把patch.dex 和⽤户⼿机⾥的旧的classes.dex⽂件合成得到修复之后的dex⽂件,然后重启加载合成新的dex⽂件 (有点像增量更新)。
(4) 美团的Qzone超级补丁⽅案,基于dex分包⽅案,把BUG⽅法修复以后,放到⼀个单独的dex补丁⽂件,让程序运⾏期间加载dex补丁,执⾏修复后的⽅法。
2:类加载机制
不管是哪⼀种热修复⽅案,都⽤到了类加载机制与反射。⾸先类加载机制的⼀个核⼼就是,双亲委托机制(双亲有歧义实际上我感觉应该叫⽗亲委托机制),意思是某个类加载器在加载类时,⾸先将加载任务委托给⽗ - 类加载器,依次递归,如果⽗类加载器可以完成类加载任务,就成功返回;只有⽗类加载器⽆法完成此加载任务或者没有⽗类加载器时,才⾃⼰去加载。我们还是⽤Android SDK 的源码来看⼀看,看⼀下ClassLoader.java⾥⾯的loadClass⽅法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded买房子注意什么
// ⾸先判断这个类是否被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 这个parent实际上就是ClassLoader的⼀个成员变量,⼀般是构造⼦类时来给它赋值
c = parent.loadClass(name, false);
} else {
//如果⼦类没有给它赋值,直接调⽤BootClassLoader来这个类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//如果⽗类加载器也不到,通过⾃⼰来
c = findClass(name);
}
}
return c;
}
我们主要看看这个findClass, 热修复我们⼀般拿到的类加载器是PathClassLoader。
findClass这个函数在PathClassLoader 不到,说明在它的⽗类⾥⾯BaseDexClassLoader⾥⾯。那么我再来看⼀看BaseDexClassLoader这个类⾥⾯的findClass⽅法。
可以看到这个成员变量pathList是⼀个对象,对应的类是DexPathList, findClass实际上是调⽤DexPathList⾥⾯的findClass,我们再继续看下源码。
dexElements⼜是个什么东西呢?
落井下石的儿子
就是DexPathList的⼀个成员变量 ,那么是在那⾥赋值的呢?继续⼀下源码。
可以看到是通过maxDexElements这个函数来赋值的,⾸先注意第⼀个参数dexPath ,这个实际上就是传⼊的我们APK⾥⾯编译打包之后的Classes.dex ⽂件的路径,所以可以知道makeDexElements这个函数的功能就是把DexFile转化成Element数组的。(注意: 有的是版本是
通过makePathElements这个函数来吧DexFile⽂件转化成数组的,不同的版本可能参数会有变化,我这个是Andoid9.0的源码)
潘玮柏国籍
可以看到这⾥⾯有⼀个for循环,遍历那个Dex File ,当我们APP的⽅法超过了65535个⽅法之后,我们⼀般都会进⾏分包,在代码⾥⾯adle配置⽂件⾥⾯配置
android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 26
男孩子取名multiDexEnabled true  //启⽤分包⽅案
}
}
然后在编译打包阶段,我们就能看到有classes.dex , classes2.dex 以及更多。
3,热修复的流程。
国产最恐怖的电影
通过上⾯⼀层⼀层的代码分析,我们可以知道,⾸先获得应⽤的PathClassLoader,这个活动很简单,我们只需要调⽤
拿到PathClassLoader之后,我们知道编译后的class⽂件是被打包成Dex⽂件的,⽽Dex⽂件⼜通过makeDexElements函数转化成
Element数组,这个数组dexElements⼜是DexPathList的⼀个成员变量 , ⽽DexPathlist ⼜位于BaseDexClassLoader⾥⾯,也是它的⼀
个成员变量,变量名是pathList ,⽽且还是⼀个私有的。所以我们热修复,就是通过反射拿到这个pathList , 然后把我们修复好的补丁包例
如叫做patch.dex ,通过makeDexElement函数转化成数组 ,并且与旧的dexElements数组合并 ,最主要的是修复的数组应该放到前⾯,
这样就会先加载到修复好的类。同时,我们应该把程序关掉重新加载,原因在于如下:
假如我们在线更新补丁包,但是之前有bug的类已经被加载过了,那么就只会⾛findLoadedClass,此时就不会⾛findClass这个⽅法, 因
此需要重启⼀下应⽤。咱以前打王者荣耀,赛季更新完之后经常会关掉程序,打开才会⽣效,就是这个原因。
4,反射的过程
招商银行信用卡中心进度查询
稍微⽤代码写写思路(伪代码)
private void ReflectFix(Context context) throws NoSuchFieldException, IllegalAccessException {
ClassLoader classLoader = ClassLoader(); //拿到当前应⽤的ClassLoader ,这种⽅式拿到的是PathClassLoader,
Class<?> clazz =  Class().getSuperclass(); // 因为得到的是PathClassLoader,我们要拿到的是BaseDexClassLoader,这个类是它的⽗类,因此通过  Field dexElements = DeclaredField("pathList"); //拿到dexElements这个数组,变量名是pathList,因为是私有的,通过getDeclaredField获得
dexElements.setAccessible(true); // 禁⽤安全检查,因为它不是public⽽是私有的,直接调⽤会报IllegalAccessException这个异常,之后就可以使⽤get⽅法拿到对象  Object dexPathList = (classLoader);
Object[] objects = makePathElements(dexPathList, optimizedDirectory,suppressedExceptions);
// 这个objects就是我们的Elements数组了,然后⽤补丁包⾥⾯的dex⽂件和app原本有的classes.dex⽂件,合并⽣成⼀个新的数组,赋值即可。
}
后⾯的流程没有细写,就是通过System.arrayCopy将数组扩容并且进⾏赋值。
以上。。。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。