ClassLoader 理解

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文件,所以这个路径必须为内部路径。

你可能感兴趣的:(ClassLoader 理解)