[关闭]
@946898963 2020-07-12T17:14:29.000000Z 字数 4309 阅读 1087

Android BaseDexClassLoader源码阅读

Android源码分析


MultiDex的源码中使用到了BaseDexClassLoader中的相关方法,所以特地学习了下BaseDexClassLoader源码。

本文并非原创,转载自:Android BaseDexClassLoader源码阅读

前言

Java的类加载使用父类加载机制,Android开发采用的同样是Java语言,不过它并没有采用JVM实现的ClassLoader类,Android内部使用的是BaseDexClassLoader、PathClassLoader、DexClassLoader三个类加载器实现从DEX文件中读取类数据,其中PathClassLoader和DexClassLoader都是继承自BaseDexClassLoader实现,这里就分析一下它的实现代码。

关于DexClassLoader和PathClassLoader的区别,建议阅读:DexClassLoader和PathClassLoader的区别

Android ClassLoader源码

从前面的Java类委托机制直到ClassLoader的findClass方法就是在父类加载器无法加载的时候自己加载类的实现,在findClass方法里会将加载类的工作委托给DexPathList类实现。

  1. public class BaseDexClassLoader extends ClassLoader {
  2. // 需要加载的dex列表
  3. private final DexPathList pathList;
  4. // dexPath要加载的dex文件所在的路径,
  5. // optimizedDirectory是dexopt将dexPath处dex优化后输出到的路径,这个路径必须是手机内部路劲,
  6. // libraryPath是需要加载的C/C++库路径
  7. // parent是父类加载器对象
  8. public BaseDexClassLoader(String dexPath, File optimizedDirectory,
  9. String libraryPath, ClassLoader parent) {
  10. super(parent);
  11. this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
  12. }
  13. @Override
  14. protected Class<?> findClass(String name) throws ClassNotFoundException {
  15. List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
  16. // 使用pathList对象查找name类
  17. Class c = pathList.findClass(name, suppressedExceptions);
  18. return c;
  19. }
  20. }

接着查看DexPathList的实现代码,在内部会根据类加载器加载路径查找dex文件,然后将它们解析成Element对象,Element对象代表的时dex文件或资源文件,它里面保存了文件对象。

  1. // Element类代表dex文件或资源文件的路径元素
  2. static class Element {
  3. private final File file;
  4. private final boolean isDirectory;
  5. private final File zip;
  6. private final DexFile dexFile;
  7. private ZipFile zipFile;
  8. private boolean initialized;
  9. // file文件,是否是目录,zip文件通常都是apk或jar文件,dexFile就是.dex文件
  10. public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
  11. this.file = file;
  12. this.isDirectory = isDirectory;
  13. this.zip = zip;
  14. this.dexFile = dexFile;
  15. }
  16. }

在DexPathList类构造的时候会首先将dexPath变量内容分隔成多个文件路径,并且根据路径查找Android中的dex和资源文件,将它们解析后存放到Element数组中。

  1. final class DexPathList {
  2. private static final String DEX_SUFFIX = ".dex";
  3. private final ClassLoader definingContext;
  4. //
  5. private final Element[] dexElements;
  6. // 本地库目录
  7. private final File[] nativeLibraryDirectories;
  8. public DexPathList(ClassLoader definingContext, String dexPath,
  9. String libraryPath, File optimizedDirectory) {
  10. // 当前类加载器
  11. this.definingContext = definingContext;
  12. ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
  13. // 根据输入的dexPath创建dex元素对象
  14. this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
  15. suppressedExceptions);
  16. if (suppressedExceptions.size() > 0) {
  17. this.dexElementsSuppressedExceptions =
  18. suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
  19. } else {
  20. dexElementsSuppressedExceptions = null;
  21. }
  22. this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
  23. }
  24. }

makeDexElements就是把前面dexPath里面解析到的路径下的文件全部遍历一遍,如果是dex文件或apk和jar文件就会查找它们内部的dex文件,将所有这些dex文件都加入到Element数组中,完成加载路径下面的所有dex解析。

  1. private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
  2. ArrayList<IOException> suppressedExceptions) {
  3. ArrayList<Element> elements = new ArrayList<Element>();
  4. // 所有从dexPath找到的文件
  5. for (File file : files) {
  6. File zip = null;
  7. DexFile dex = null;
  8. String name = file.getName();
  9. // 如果是文件夹,就直接将路径添加到Element中
  10. if (file.isDirectory()) {
  11. elements.add(new Element(file, true, null, null));
  12. } else if (file.isFile()){
  13. // 如果是文件且文件名以.dex结束
  14. if (name.endsWith(DEX_SUFFIX)) {
  15. try {
  16. // 直接从.dex文件生成DexFile对象
  17. dex = loadDexFile(file, optimizedDirectory);
  18. } catch (IOException ex) {
  19. System.logE("Unable to load dex file: " + file, ex);
  20. }
  21. } else {
  22. zip = file;
  23. try {
  24. // 从APK/JAR文件中读取dex文件
  25. dex = loadDexFile(file, optimizedDirectory);
  26. } catch (IOException suppressed) {
  27. suppressedExceptions.add(suppressed);
  28. }
  29. }
  30. } else {
  31. System.logW("ClassLoader referenced unknown path: " + file);
  32. }
  33. if ((zip != null) || (dex != null)) {
  34. elements.add(new Element(file, false, zip, dex));
  35. }
  36. }
  37. return elements.toArray(new Element[elements.size()]);
  38. }

在findClass方法里查找名称为name的类时只需要遍历Element数组找是dexFile就直接调用DexFile.loadClassBinaryName方法,这个方法能够从dex文件数据中生成Class对象。

  1. // 加载名字为name的class对象
  2. public Class findClass(String name, List<Throwable> suppressed) {
  3. // 遍历从dexPath查询到的dex和资源Element
  4. for (Element element : dexElements) {
  5. DexFile dex = element.dexFile;
  6. // 如果当前的Element是dex文件元素
  7. if (dex != null) {
  8. // 使用DexFile.loadClassBinaryName加载类
  9. Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
  10. if (clazz != null) {
  11. return clazz;
  12. }
  13. }
  14. }
  15. if (dexElementsSuppressedExceptions != null) {
  16. suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
  17. }
  18. return null;
  19. }

总结

BaseDexClassLoader在构造的时候会先将dexPath使用“:”分隔开,然后遍历每个路径下面的所有文件,查找到.dex文件.apk.jar类型的文件并将它们保存在Element数组中,当程序需要加载类的时候会直接遍历所有的Element对象,查找到和dex文件相关的Element就直接加载数据生成Class对象。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注