热修复这个技术点最近有点火,有QQ空间开发团队为其背书,还有的大厂开源的热修复框架,这些对于推进这项技术也起了很大的做用。做为一个有追求的工程师菜鸟,今天,我想经过几篇文章把这种在线修复的解决思路以及几种具体的实现方案理一遍。java
在开始分析以前,首先须要说说热修复解决了一个什么问题,闭上眼睛,想象一个场景:当你的产品刚上线,发现有一个致使闪退的空指针异常的问题或者程序重大漏洞急需解决,那么问题来了,怎么修复?常规的,固然是修改完有问题的方法,而后赶忙再打包,推送到用户强制更新,可是,如今还有另外一种方式,就是程序在线下载修复文件到本地,而后用修改过的类覆盖原来有问题的类,这样的用户体验是一颗赛艇的。android
好了,回到热修复这个话题。网络
首先我想先谈谈热修复自己,热修复是一种动态修复程序解决问题的思想,其自己是有不少不一样的具体实现方案的,阿里的基于C/C++层操控method指针的Dexposed,AndFix,以及QQ空间的基于dex分包的HotFix,后者和前者的热修复方案在原理上大相径庭,能够说各有千秋。而我在查阅资料的时候,发现不少Blog都不够严谨,每每标题声称热修复技术可是只解释QQ空间的解决方案,能够说这种作法是容易误导人的,虽然不能算错误,可是不太严谨。框架
目前的热修复技术的解决方案有不少,我想就上面提到的两种解决方案来作详细的探讨。ide
他们都作了大体三件事:优化
以Dexposed为例:spa
至于具体的代码解释,请直接看Android中免Root实现Hook的Dexposed框架实现原理解析以及如何实现应用的热修复3d
这两种热修复框架的区别在于:指针
这个解决方案很巧妙,基于Google推出的的Multidex方案,以ClassLoader的方式完成对问题类的替换。code
因此这个问题必定会先谈Android的分包方案:为了解决Android4.x系统中65536的方法数限制,Android推出Multidex方案,将一个完整的APK中的Dex拆分红好几个dex,经过PathClassLoader 这个加载器来加载。
当点开程序的时候,PathClassLoader 会把分包的多个dex添加到父类中的一个DexPathList 中
DexPathList 详情以下:
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
}复制代码
/*package*/ final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String APK_SUFFIX = ".apk";
/** class definition context */
private final ClassLoader definingContext;
/** list of dex/resource (class path) elements 也就是dex列表咯*/
private final Element[] dexElements;
/** list of native library directory elements */
private final File[] nativeLibraryDirectories;复制代码
那么当须要加载某个类的时候,是怎么加载的呢?
//BaseDexClassLoader:
@Override
protected Class< ?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}复制代码
findClass()方法以下:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}复制代码
如你所见,当须要加载一个类的时候,会在pathList中去寻找,而且是经过顺序遍历各个dex包的方式,一旦找到目标类,则中止遍历。
这就给了咱们一个想法,有没有可能把打了补丁的dex插入到pathList中,当须要加载有问题的类的时候,根据遍历,首先查到已经修复的类,遍历结束,也就完成了修复。(固然了,这个想法是腾讯空间Android工程师想到的)
有了想法,也得有合适的加载器啊。结果你猜怎么着?Android还真提供了这样的机会。
在Android中也有三个类加载器,分别是UrlClassLoader,PathClassLoader,DexClassLoader.
好了,基本机制到这里就结束了,还有一些问题却没有被提出来,不过网络上已经有了很好的解决方案了。
虽然如今咱们公司的开发团队确定用不上热修复技术,可是做为工程师却必须对新技术有所研究。近期我会继续研究热修复 HotFix 框架的源码,有必要的话会对DexClassLoader如何动态的插入jar包或者dex文件给出更详细的解析,暂时没有时间解释了,你们先上车
卧槽 说错话了.....