Android热修复的方式
关于动态热修复,目前发现的思路集中在三点:
方式1:既然是热修复,那么就是新的类替换掉旧的类,而java世界类的加载是通过类加载器来实现,而Android应用程序的默认类加载器PathClassLoader加载的时候其实是通过关联的DexFile来实现(动态加载的时候已经反复涉及,这里不再赘述),当有多个Dex文件的时候,是按照dexElements中的顺序来执行,如果一旦加载到则返回该类,否则遍历查询dexElements中的所有元素,直到最终查询完,那么基于这个事实,则可以将新类的dex文件加载的时候对应的Element放置在最前面,这样PathClassLoader加载的时候就自然先加载到新类而不是旧的有问题的类,但是这种机制在多个mutildex时存在缺陷,具体请参见下面转发链接的阐述,另外一旦某个类已经被加载,则失效了,因为一旦被加载之后,PathClassLoader的loadClass已经直接返回class而不会去查询,这样使得往往下载修复包之后需要在下次启动才能加载。当然这个办法基于纯java实现,非侵入性,与平台无关;
方式2:基于阿里的AndFix开源项目,这个的出发点是另外一个角度,类被类加载器加载进来之后,那么其实本质还是虚拟机在管理,而根据java虚拟机包括Dalvik虚拟机的内存模型(可以查看我那本深入理解java虚拟机书籍相关章节),类的class文件加载进来之后存放在方法区,那么既然已经认定这个类被加载了,怎么做到热修复,阿里的方式确实巧妙,因为java虚拟机本身是C++语言实现的,通过JNI的方式可以获取到方法区中方法的引用,修改方法内部的执行引用,具体的还需要更加深入研究,将方法的执行逻辑指向了新方法,这种情况下可以做到及时加载及时修复,不用重启应用,缺点是通过JNI方式,so的兼容性决定了适用性,不过阿里的做得已经不错,当然一旦Android平台的虚拟机内部实现改变,就需要重新修改so对应的C++代码,目前首选这个方式;
另外,生成新旧dex的patch也是一个关键点,阿里提供了这样一个工具:AppPatch。生成patch的过程,大概是基于smali和baksmali,先加载新的和旧的两个dex文件得到所有的class,然后通过getImplementation()方法可对比他们的方法实现是否一致,将对比结果分成添加的方法/字段和修改的方法/字段分别保存起来,在重新生成的差异化dex文件时,根据对比结果在相应的方法或者字段前添加注解标志。要深入理解的话需要熟悉一下smali/baksmali的源码以及dalvik虚拟机的文件结构。
- 方式3:阿里的Dexposed开源项目,也是纯java实现,基于大名鼎鼎的 Xposed framework project开源项目,面向AOP切面编程的思想,目前还没有很深入研究,可能与方式1有些类似,也可能基于动态代理等方式来做到。不过目前这个项目只能适用于5.0以下系统,目前不建议使用。