|
|
|
---
|
|
|
|
热修复
|
|
|
|
---
|
|
|
|
|
|
|
|
#### 目录
|
|
|
|
|
|
|
|
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)
|