美文网首页
ClassLoader问题汇总

ClassLoader问题汇总

作者: AndroidHint | 来源:发表于2018-03-21 21:53 被阅读0次

    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的双亲委托模型在热修复中的应用。

    相关文章

      网友评论

          本文标题:ClassLoader问题汇总

          本文链接:https://www.haomeiwen.com/subject/njdpqftx.html