---
热修复
---

#### 目录

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<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;
    }
	//...
}
```

其实 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<File> files, File optimizedDirectory,
            List<IOException> 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<Throwable> 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<String> getDexPaths() {
        List<String> dexPaths = new ArrayList<String>();
        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<Throwable> 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)