--- 热修复 --- #### 目录 1. 思维导图 2. 概述 3. ClassLoader - BaseDexClassLoader - PathClassLoader - DexClassLoader - SecureClassLoader - URLClassLoader 4. 原理浅析 - BaseDexClassLoader - PathClassLoader - DexClassLoader - DexPathList 5. 实战 6. 参考 #### 思维导图 ![](https://i.loli.net/2019/01/26/5c4c5bc8bd779.png) #### 概述 一句话来说就是 dex 插桩。 在加载 dex 文件的时候,是放在一个 Element[] 数组里的,我们只要把修复好的 classes.dex 文件放在数组最前面被优先加载,那在加载有 bug 的 dex 文件里的 class 类的时候已经被加载过了,系统就不会加载,这时候带有 bug 的 class 就算被 “修复” 了。 #### ClassLoader ClassLoader 是个抽象类,其具体的实现的子类有 BaseDexClassLoader 和 SecureClassLoader。 ##### BaseDexClassLoader 它也有两个子类,分别是 PathClassLoader 和 DexClassLoader。 其中 PathClassLoader 只能加载安装在 Android 系统内 APK 文件(/data/app 目录下),其他位置的文件加载时都会报 ClassNotFoundException。因为 PathClassLoader 会读取 /data/dalvik-cache 目录下的经过 Dalvik 优化过的 dex 文件,这个目录的 dex 文件是在安装 apk 包的时候由 Dalvik 生成的,没有安装的时候,自然没有生成这个文件。 DexClassLoader 可从任意目录加载 jar/apk/dex。 ##### URLClassLoader 只能用来加载 jar 文件,这在 Android 的 Dalvik/ART 上没法使用。 #### 原理浅析 热修复涉及的就是 dex 的加载流程。涉及的类有 BaseDexClassLoader、DexClassLoader、PathClassLoader、DexPathList 等等。 ##### BaseDexClassLoader ```java public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; /** * dexPath: 要加载的程序文件,一般是 dex 文件 * optimizedDirectory: dex 文件解压目录 * librarySearchPath: 加载程序文件时需要用到的库路径 * parent: 父加载器 */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); if (reporter != null) { reporter.report(this.pathList.getDexPaths()); } } //... @Override protected Class findClass(String name) throws ClassNotFoundException { List suppressedExceptions = new ArrayList(); 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; } //... } ``` 其实 BaseDexClassLoader 的 findClass 是调用 DexPathList 的 findClass 方法,如果为 null 就抛出 ClassNotFoundException。 ##### PathClassLoader ```java public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } } ``` ##### DexClassLoader ```java public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); } } ``` 可以看到,其实 DexClassLoader 和 PathClassLoader 都没有做什么,都是调用父类方法,只不过 DexClassLoader 多传了一个optimizedDirectory,它们的使用场景不一样: PathClassLoader:用于加载 Android 系统类和开发编写应用的类,只能加载已经安装应用的 dex 或 apk 文件,是 Android 默认使用的类加载器,也是 getSystemClassLoader 的返回对象。 DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,是实现热修复的重点。 BootClassLoader:主要用于加载系统的类,包括 java 和 android 系统的类库,和 JVM 中不同,BootClassLoader 是 ClassLoader 内部类,是由 Java 实现,它也是所有系统 ClassLoader 的 父 ClassLoader。 ##### DexPathList ```java final class DexPathList { private static final String DEX_SUFFIX = ".dex"; private static final String zipSeparator = "!/"; private final ClassLoader definingContext; /** * dex 文件数组 */ private Element[] dexElements; public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) { //... } /** * BaseDexClassLoader 中实例化的是这个构造方法 */ public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) { //... this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext); //... } /** * 生成 Element 集合,Element 里保存着 DexFile 和 文件路径 * 遍历该 APP 私有目录所有文件,拿到以 .dex 结尾的文件 */ private static Element[] makeDexElements(List files, File optimizedDirectory, List suppressedExceptions, ClassLoader loader) { Element[] elements = new Element[files.size()]; int elementsPos = 0; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { DexFile dex = null; try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } /** * 加载 dex 文件 */ private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException { if (optimizedDirectory == null) { return new DexFile(file, loader, elements); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); } } /** * */ public Class findClass(String name, List suppressed) { for (Element element : dexElements) { Class clazz = element.findClass(name, definingContext, suppressed); if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } /** * dex 文件路径集合 */ /*package*/ List getDexPaths() { List dexPaths = new ArrayList(); for (Element e : dexElements) { String dexPath = e.getDexPath(); if (dexPath != null) { dexPaths.add(dexPath); } } return dexPaths; } static class Element { private final File path; private final DexFile dexFile; //... public Class findClass(String name, ClassLoader definingContext, List suppressed) { return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null; } } } ``` #### 实战 #### 参考 [热修复——深入浅出原理与实现](https://juejin.im/post/5a0ad2b551882531ba1077a2) [https://github.com/GitLqr/HotFixDemo](https://github.com/GitLqr/HotFixDemo) [网易云课堂]( https://study.163.com/course/courseLearn.htm?courseId=1208968811#/learn/live?lessonId=1278455246&courseId=1208968811) [热修复——Tinker 的集成与使用](https://mp.weixin.qq.com/s/xpB_ipYv9cN8k8fdr_7wCw) [剖析ClassLoader深入热修复原理](https://www.jianshu.com/p/95387cc07e3c)