likes
comments
collection
share

ClassLoader问题汇总

作者站长头像
站长
· 阅读数 36

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

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

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。

PathClassLoader

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

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这个类。

DexPathList

首先看到DexPathList的构造函数

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方法。

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包。

2、简述双亲委托模型

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

使用双亲委托模型的优点

1、可以避免类的重复加载。当父加载器已经加载了此类,子类加载器就不用再加载一遍,否则会形成类的重复加载。 2、考虑到安全方面,如果不使用双亲委托模型,那么如果我们自定义一个ClassLoader去加载一个系统类,例如java.lang.Object。如果没有双亲委托模型,那么我们自定义的ClassLoader就会去加载这个类。

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

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

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

热修复目前有三种方案,分别是底层替换方案、类加载方案和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的双亲委托模型在热修复中的应用。

转载自:https://juejin.cn/post/6844903944167096327
评论
请登录