ClassLoader问题汇总

一、Android中有哪几种ClassLoader?它们的做用和区别是什么?

从源码中能够看到,Android中有三个ClassLoader,分别是BaseDexClassLoader、PathClassLoader、DexClassLoader。 java

ClassLoader的子类

从上图能够看出,ClassLoader的直接子类是BaseDexClassLoader、SecureClassLoader;间接子类是DelegateLastClassLoader、DexClassLoader、InMemoryDexClassLoader、PathClassLoader、URLClassLoader。 咱们比较经常使用的是BaseDexClassLoader、DexClassLoader和PathClassLoader。数组

BaseDexClassLoader

DexClassLoader和PathClassLoader都继承自BaseDexClassLoader。DexClassLoader和PathClassLoader的主要功能都放在了BaseDexClassLoader中。看一下BaseDexClassLoader的构造函数:安全

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
复制代码

参数有四个,分别是: String dexPath,指要加载的apk或者jar包的路径。最终要将该dexPath路径上的文件ODEX优化到optimizedDirectory文件夹中,而后再对ODEX后的文件进行加载。 File optimizedDirectory,dexPath路径上的文件须要优化到的文件夹。 String libraryPath,apk或者jar包中的C或者C++库所存放的路径。 ClassLoader parent,该ClassLoader的父类ClassLoader,通常为当前执行类的ClassLoader。bash

PathClassLoader

PathClassLoader继承自BaseDexClassLoader,看一下它的构造函数:ide

public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
  super(dexPath, null, libraryPath, parent);
}
复制代码

构造函数直接调用了BaseDexClassLoader的构造函数,注意其中的第二个参数传了null。函数

DexClassLoader

DexClassLoader继承自BaseDexClassLoader,看一下它的构造函数:优化

public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
  super(dexPath, new File(optimizedDirectory), library, parent);
} 
复制代码

构造函数直接调用了BaseDexClassLoader的构造函数,咱们对比一下PathClassLoader的构造函数,能够看到除了第二个参数不一样以外,其余参数都是同样的。 而咱们知道PathClassLoader只能够加载Android系统类和应用的类。而DexClassLoader不单单能够加载Android系统类和应用的类,而且能够加载外部jar包和Apk包的类。 所以,咱们猜测可能跟第二个参数不一致致使的。 再返回到BaseDexClassLoader中看第二个参数的做用,能够看到在BaseDexClassLoader中使用optimizedDirectory构造了DexPathList对象。那么咱们来分析一下DexPathList这个类。ui

DexPathList

首先看到DexPathList的构造函数this

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {        
        。。。省略     
        this.definingContext = definingContext;
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
  }
复制代码

其中optimizedDirectory参数用做了makeDexElements方法的参数,那咱们再去看makeDexElements方法。spa

private static Element[] makeDexElements(ArrayList<File> files,
            File optimizedDirectory) {
        ArrayList<Element> elements = new ArrayList<Element>();       
        for (File file : files) {
            ZipFile zip = null;
            DexFile dex = null;
            String name = file.getName();
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                try {
                    zip = new ZipFile(file);
                } catch (IOException ex) {
                    
                }
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ignored) {
                    
                }
            } else {
                System.logW("Unknown file type for: " + file);
            }
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, zip, dex));
            }
        }
        return elements.toArray(new Element[elements.size()]);
    }
复制代码

这里又调用了loadDexFile方法,

private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }
复制代码

能够看到当optimizedDirectory为null时,直接new了一个DexFile对象,而不为null时,调用了DexFile的loadDex静态方法。在DexFile的loadDex最终new了一个DexFile对象,而且在DexFile的构造函数中调用了openDexFile方法。该方法中的原型以下:

native private static int openDexFile(String sourceName, String outputName,
        int flags) throws IOException;
复制代码

能够看到PathClassLoader这里应该为null,而DexClassLoader这里应该是dex优化后的路径。因此PathClassLoader没有设置Dex优化后的存放路径。其实optimizedDirectory为null时的默认路径就是/data/dalvik-cache 目录。 因此这就是PathClassLoader只能加载Android系统或者应用的类的缘由,而DexClassLoader能够加载外部jar包或者Apk包的缘由。

总结

Android中一共有三种ClassLoader,分别是BaseDexClassLoader、PathClassLoader、DexClassLoader。PathClassLoader和DexClassLoader都继承于BaseDexClassLoader,PathClassLoader只能加载Android系统和应用内的类,而DexClassLoader除了能够加载Android系统和应用内的类外,还能够加载外部的apk和jar包。

二、简述双亲委托模型

Android的双亲委托模型就是当某个类加载器接到加载某个类的请求时,其首先会将加载任务委托给其父加载器,以此递归,若是父加载器能够完成加载任务,则返回,不然再使用该加载器进行类的加载。

使用双亲委托模型的优势

一、能够避免类的重复加载。当父加载器已经加载了此类,子类加载器就不用再加载一遍,不然会造成类的重复加载。 二、考虑到安全方面,若是不使用双亲委托模型,那么若是咱们自定义一个ClassLoader去加载一个系统类,例如java.lang.Object。若是没有双亲委托模型,那么咱们自定义的ClassLoader就会去加载这个类。

Android的类加载是否必定会遵循双亲委托模式

并非的,双亲委托模式只是JDK提供的ClassLoader类的实现方式。在实际开发中,咱们能够经过自定义ClassLoader,并重写父类的loadClass方法,就能够打破这一机制。

三、简述双亲委托模型在热修复领域的应用

热修复目前有三种方案,分别是底层替换方案、类加载方案和Instant Run方案。 而双亲委托模型在热修复上面的应用主要体如今类加载方案上面。因此咱们这里重点看一下怎么使用类加载方案来实现热修复。 从上面ClassLoader加载类的过程当中,咱们知道类是由DexPathList中的findClass方法来加载类的。看一下findClass方法:

public Class<?> findClass(String name, List<Throwable> suppressed) {
      for (Element element : dexElements) {//1
          Class<?> clazz = element.findClass(name, definingContext, suppressed);//2
          if (clazz != null) {
                return clazz;
          }
      }
      if (dexElementsSuppressedExceptions != null) {     

            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
      }
      return null;
  }
复制代码

Element内部封装了DexFile,DexFile用于加载dex文件,因此dex文件和Element是一一对应的。当有多个dex文件时,就会有多个Element对象,造成Element数组。当须要查找类时,会去遍历这个数组,其实至关于去遍历dex文件数组,而后经过Element的findClass方法去查找类。当查找到相应的类后,就返回,不然继续使用下一个Element来查找。 上面的加载流程其实就是双亲委托模型,因此咱们能够将修复后的类patch.class打包成包含dex的补丁包,并将其放在Element数组的第一个位置,这样当去查找该类的时候,就先会去修复后的dex中查找到修复后的类,然后面有问题的类并不会被查找到。这就是ClassLoader的双亲委托模型在热修复中的应用。

相关文章
相关标签/搜索