parent
c2bf7f960d
commit
03ad51bf50
@ -0,0 +1,266 @@ |
||||
--- |
||||
热修复 |
||||
--- |
||||
|
||||
#### 目录 |
||||
|
||||
1. 思维导图 |
||||
2. 概述 |
||||
3. 原理浅析 |
||||
- BaseDexClassLoader |
||||
- PathClassLoader |
||||
- DexClassLoader |
||||
- DexPathList |
||||
4. 实战 |
||||
5. 参考 |
||||
|
||||
#### 思维导图 |
||||
|
||||
![](https://i.loli.net/2019/01/26/5c4c5bc8bd779.png) |
||||
|
||||
#### 概述 |
||||
|
||||
一句话来说就是 dex 插桩。 |
||||
|
||||
在加载 dex 文件的时候,是放在一个 Element[] 数组里的,我们只要把修复好的 classes.dex 文件放在数组最前面被优先加载,那在加载有 bug 的 dex 文件里的 class 类的时候已经被加载过了,系统就不会加载,这时候带有 bug 的 class 就算被 “修复” 了。 |
||||
|
||||
#### 原理浅析 |
||||
|
||||
热修复涉及的就是 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。 |
||||
|
||||
##### DexClassLoader |
||||
|
||||
##### 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 系统中的 apk 文件,是 Android 默认使用的类加载器。 |
||||
|
||||
DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader,是实现热修复的重点。 |
||||
|
||||
##### 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) |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 53 KiB |
Loading…
Reference in new issue