Android的类加载机制遵循Java的双亲委派原理。其继承关系如下:
ClassLoader <--- BaseDexClassLoader <---PathClassLoader、DexClassLoader
PathClassLoader和DexClassLoader的源码如下:
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);
}
}
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@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;
}
...
}
PathClassLoader和DexClassLoader的区别仅仅在于构造方法中的optimizedDirectory是否为空,这里先不讨论optimizedDirectory的作用,后续会详细介绍。
从BaseDexClassLoader的源码中可以看出来,加载一个类的时候,都是从pathList(DexPathList的实例)中查找的。DexPathList的findClass()方法源码如下:
public Class findClass(String name, List suppressed) {
//从dexElements数组中查找,dexElements就是用于存储dex文件信息,dexElements在BaseDexClassLoader的构造方法中创建。
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
DexFile执行了dex文件的class查找工作,其loadClassBinaryName()方法源码如下:
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
其中defineClassNative()方法是一个native方法后面会专门介绍。
Android的ClassLoader加载机制可以总结为一句话:从遍历dexElements数组,挨着查找每个dex文件中是否有待加载打Class文件,如果有,则加载,如果没有,则接着查找下一个dex文件,直到将dexElements数组遍历完毕。很多插件化和热修复技术就是基于这个基本原则来设计。以热修复为例,将待修复的类打包成jar包,在应用启动的时候,将jar包解析成dex文件,并插入到dexElements数组的头部。这样等到待修复的类加载时,永远查找到的是dexElements头部第一个dex文件中的已经修复过的类。热修复的原理就这么简单,当然,热修复的实现方式还有很多,这里不一一说明。基于在dexElements数组头部插入的方式还有一个问题。那就是CLASS_ISPREVERIFIED标记。CLASS_ISPREVERIFIED的问题后面章节会详细说明。