1. 老祖宗
Android 中类加载器有 BootClassLoader,URLClassLoader,PathClassLoader,DexClassLoader,BaseDexClassLoader, 等都最终继承自 java.lang.ClassLoader。
所以 ClassLoader 类就是老祖宗。
这个老祖宗开始是无参数构造
180public abstract class ClassLoader {
181
182 static private class SystemClassLoader {
183 public static ClassLoader loader = ClassLoader.createSystemClassLoader();
184 }
185}
这里的 loadClass 函数一般不会被继承类重写,这个也是所有类加载的具体函数
359 protected Class> loadClass(String name, boolean resolve)
360 throws ClassNotFoundException
361 {
362 // First, check if the class has already been loaded
363 Class> c = findLoadedClass(name); // 判断是否已经被加载
364 if (c == null) {
365 try {
366 if (parent != null) {
367 c = parent.loadClass(name, false); // 父类寻找
368 } else {
369 c = findBootstrapClassOrNull(name);
370 }
371 } catch (ClassNotFoundException e) {
372 // ClassNotFoundException thrown if class not found
373 // from the non-null parent class loader
374 }
375
376 if (c == null) {
377 // If still not found, then invoke findClass in order
378 // to find the class.
379 c = findClass(name); // 具体找类
380 }
381 }
382 return c;
383 }
可以看出在加载类时首先判断这个类是否之前被加载过,如果有则直接返回,如果没有则首先尝试让parent ClassLoader进行加载,加载不成功才在自己的findClass中进行加载。
findClass()
函数在具体的继承类中实现,一般是在 BaseDexClassLoader 内实现
2. 大儿子 BootClassLoader、URLClassLoader
BootClassLoader 是 ClassLoader 内部类,内部可见,无法使用。
URLClassLoader 只能加载 jar 所以 Android 中无法使用
3. 乖儿 BaseDexClassLoader
PathClassLoader 和 DexClassLoader 都继承自 BaseDexClassLoader, 其中的主要逻辑都是在 BaseDexClassLoader 完成的
# BaseDexClassLoader 类
62 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
63 String librarySearchPath, ClassLoader parent) {
64 super(parent);
65 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
66
67 if (reporter != null) {
68 reporter.report(this.pathList.getDexPaths());
69 }
70 }
上面看到生成了 pathList 变量对象。
下面来实现 findClass 函数:
88 @Override
89 protected Class> findClass(String name) throws ClassNotFoundException {
90 List suppressedExceptions = new ArrayList();
91 Class c = pathList.findClass(name, suppressedExceptions);
92 if (c == null) {
93 ClassNotFoundException cnfe = new ClassNotFoundException(
94 "Didn't find class \"" + name + "\" on path: " + pathList);
95 for (Throwable t : suppressedExceptions) {
96 cnfe.addSuppressed(t);
97 }
98 throw cnfe;
99 }
100 return c;
101 }
这里是在调用刚生成的 pathList 对象内的方法,这个对象是 DexPathList
对象。 看下 findClass 方法:
464 public Class> findClass(String name, List suppressed) {
465 for (Element element : dexElements) {
466 Class> clazz = element.findClass(name, definingContext, suppressed);
467 if (clazz != null) {
468 return clazz;
469 }
470 }
471
472 if (dexElementsSuppressedExceptions != null) {
473 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
474 }
475 return null;
476 }
这里看到是遍历 dexElements 数组,然后拿到成员变量后,去调用 Element 的对象的 findClass 方法。也就是 pathList 对象里面有一个 Element 对象的数组,所有的类在寻找的时候都会找到这里。下面看下 Element 类的方法:
675 public Class> findClass(String name, ClassLoader definingContext,
676 List suppressed) {
677 return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
678 : null;
679 }
680
上面可以看到,最后是调用了 dexFile 类的 loadClassBinaryName 方法,现在跳进 DexFile 类中:
274 public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
275 return defineClass(name, loader, mCookie, this, suppressed);
276 }
278 private static Class defineClass(String name, ClassLoader loader, Object cookie,
279 DexFile dexFile, List suppressed) {
280 Class result = null;
281 try {
282 result = defineClassNative(name, loader, cookie, dexFile);
283 } catch (NoClassDefFoundError e) {
284 if (suppressed != null) {
285 suppressed.add(e);
286 }
287 } catch (ClassNotFoundException e) {
288 if (suppressed != null) {
289 suppressed.add(e);
290 }
291 }
292 return result;
293 }
384 private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
385 DexFile dexFile)
上面看到依次调用了 loadClassBinaryName -> defineClass -> defineClassNative 这些方法,最终转到 native 层的代码去加载类,并最终返回类实例。
- 上面就是具体寻找类的流程。
4. 大孙子 DexClassLoader
继承了 BaseDexClassLoader 类,DexClassLoader支持加载APK、DEX和JAR,也可以从SD卡进行加载
36public class DexClassLoader extends BaseDexClassLoader {
55 public DexClassLoader(String dexPath, String optimizedDirectory,
56 String librarySearchPath, ClassLoader parent) {
57 super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
58 }
59}
5. 小孙子 PathClassLoader
可以看到这里并没有设置 optimizedDirectory 路径,在设置为 null 时,默认的 odex 路径是: /data/dalvik-cache 目录
25public class PathClassLoader extends BaseDexClassLoader {
37 public PathClassLoader(String dexPath, ClassLoader parent) {
38 super(dexPath, null, null, parent);
39 }
63 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64 super(dexPath, null, librarySearchPath, parent);
65 }
66}
optimizedDirectory 参数的说明:
这个路径就是从 apk 解压出来的 dex 的设定路径。
File optimizedDirectory,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件,因为你提取出来了,系统第一次启动的时候就不用去解压程序压缩包的程序,少了一个解压的过程。这样的话系统启动就加快了。为什么说是第一次呢?是因为DEX版本的也只有第一次会解压执行程序到 /data/dalvik-cache(针对PathClassLoader)或者optimizedDirectory(针对DexClassLoader)目录,之后也是直接读取目录下的的dex文件,所以第二次启动就和正常的差不多了。当然这只是简单的理解,实际生成的ODEX还有一定的优化作用。ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须为内部路径。