主流的热修复方案:
1. 底层替换 - AndFix
在运行时替换掉底层有Bug的方法的地址,将他们的指针指向修复之后的方法的内存地址,从而实现热修复的功能。
底层替换方案限制较多,实现难度大,难于控制,容易产生方法的野指针;时效性号,加载轻快。立即生效。
2. 类加载方案 - Tinker、QZone
利用Android中类加载机制中的dexElements,将修复之后的dex文件放置到dexElements前面,屏蔽掉有问题的dex文件的加载,从而实现热修复的功能。
类加载方案时效性较差,因为Java的双亲委派机制的原因,首次打开不会重复加载类,需要再次打开才能生效,修复范围广,实现简单,易于控制。
动态加载dex实现热修复
Android中的类加载是通过DexClassLoader,在DexClassLoader中使用DexpathList将所有的dex文件加载到dexElements数组中,在进行.class加载的时候,会从dexElements数组中查找当前.class属于那个dex,然后从该dex中加载.class。
如上图,我们将一个修复后的Class文件HotFixTest文件打包成一个patch.dex文件,在App启动的时候,动态的将其加载到dexElements的最开始位置,这样在App加载的时候就会优先的加载这个热修复的类,从而实现dex热修复。
实现这一方案需要三步:
Tinker热修复原理
热修复的实现过程:
Tinker会创建一个TinkerClassLoader类加载器,在这个类加载器中尝试加载class(loadClass),要是TinkerClassLoader加载不到class文件,会将加载任务提交到PathClassLoader。
Tinker会通过反射机制替换掉Application中的ClassLoader,同样的也是利用反射机制替换掉Resource中的ClassLoader,从而接管App中类加载的工作。
Android在加载类的时候,会遍历pathList对象的dexElements数组,该数据中是apk下所有dex文件的信息,在开始加载class文件的时候,会遍历dexElements数组,查看class文件在那个dexElementItem中,从而加载该dexElementItem对象的dex文件。
Tinker通过反射机制拿到pathList对象,然后通过pathListField.get()方法拿到了pathList中原有的dexElements数组,然后将我们要替换的dex文件数组additionalClassPathEntries和原有的dex文件数组合并,下面代码中的expandFieldArray方法,这一个过程就是dex文件的动态插队的过程:
上面是Tinker替换class文件的过程,对于so的库,Tinker同样是通过反射机制拿到pathList对象的nativeLibraryDirectories数组,该数组中保存了App需要使用的so文件。Tinker在拿到这个数组之后,将本次要替换的lib库动态插入到nativeLibraryDirectories数组中。
在Android10上禁止了dex文件的动态加载,所以无法对基准版本和patch文件的dex进行合并优化,从而无法实现类替换的功能,Tinker针对Android10,通过反射机制PackageManagerService的registerDexMode方法,强制系统触发dex文件的合并优化。