类加载机制-概念
主要是应用了和Java相似的双亲委派模型,可以参考这篇文章Java虚拟机-类加载机制 。
其中验证要加载的多个类都是同一个类的成立条件为,
- 相同的
className - 相同的
packageName - 被相同的
classLoader加载
Android中的类加载器
类型
Android主要的类加载器有四个,
BootClassLoader,加载Android Framework层中的class字节码文件(与Java的Bootstrap ClassLoader类似)PathClassLoader,加载已经安装到系统中apk的class字节码文件(与Java的Application ClassLoader类似)DexClassLoader,加载制定目录的class字节码文件(与ava中的Custom ClassLoader类似)BaseDexClassLoader,PathClassLoader和DexClassLoader的父类
他们之间的继承关系,

源码
loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 步骤一
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//步骤二
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 步骤三
c = findClass(name);
}
}
return c;
}
通过源码可以看出,
- 调用loadClass方法,先在当前类加载器中查找是否已经加载了该类,有则返回该类,没有跳到步骤2
- 调用父类加载器对应的loadClass方法,重复步骤一,直到顶层类加载器
- 如果所有的类加载器都没有加载过该类,则调用findClass去dex文件中加载这个class
PathClassLoader与DexClassLoader
PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
// libcore/dalvik/src/main/java/dalvik/system/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:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
类加载过程
通过上述分析,DexClassLoader与PathClassLoader其具体实现都是通过他们的父类来完成的,我们先看看BaseDexClassLoader的构造函数,
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
...
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent){
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
...
}
参数 optimizedDirectory 需要重点提一下,
应用程序在第一次被加载的时候,为了提高以后的启动速度和执行效率,Android 系统会对 dex 相关文件做一定程度的优化,并生成一个 odex 文件,此后再运行这个应用程序的时候,只要加载优化过的 odex 文件就行了,省去了每次都要优化的时间,而参数 optimizedDirectory 就是代表存储 odex 文件的路径,这个路径必须是一个内部存储路径。
PathClassLoader 没有参数 optimizedDirectory,这是因为 PathClassLoader 已经默认了参数 optimizedDirectory 的路径为:/data/dalvik-cache。
findClass
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 实际调用的是DexPathList.findClass()方法来获取class
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;
}
在DexPathList中,他的构造函数需要重点分析,
private final Element[] dexElements;
//简化过的代码
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
//构造了一个Element对象的数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
...
}
在makeDexElements方法中,以遍历dex文件的方式,将(dex、apk、jar、zip等等文件)封装成Element对象,最后作为Elements数组返回,
//简化过的代码
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
// 1.创建Element集合
ArrayList<Element> elements = new ArrayList<Element>();
// 2.遍历所有dex文件(也可能是jar、apk或zip文件)
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
...
// 如果是dex文件
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile(file, optimizedDirectory);
// 如果是apk、jar、zip文件,不同的Android版本处理方式不同
} else {
zip = file;
dex = loadDexFile(file, optimizedDirectory);
}
...
// 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
// 4.将Element集合转成Element数组返回
return elements.toArray(new Element[elements.size()]);
}
DexPathList构造好了Element数组后,我们来看看它的findClass方法,
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
// 遍历得到一个dex文件
DexFile dex = element.dexFile;
if (dex != null) {
// 在dex文件中查找类名是参数name的类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
最终通过DexFile.loadClassBinaryName在dex文件中查找到对应的class类,如果找不到则返回null。
热修复原理
综上可以看出,热修复核心逻辑是在DexPathList.findClass()过程中,一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。
也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。当然有bug的class仍然存在,但因为类加载机制没有办法被重新加载而已。