Android ClassLoader详解

来源:互联网 发布:三菱fx5u编程手册 编辑:程序博客网 时间:2024/06/07 18:34

Classloader概述:

Classloader种类,如下图:(原图链接)

这里写图片描述

一般而言 :我们一个app至少要用到PathClassLoader和BootClassLoader;

特点和作用:

特点

loadClass方法在加载一个类的实例的时候,

  1. 会先查询当前ClassLoader实例是否加载过此类,有就返回;
  2. 如果没有。查询Parent是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类;
  3. 如果继承路线上的ClassLoader都没有加载,才由Child执行类的加载工作;

这样做有个明显的特点,如果一个类被位于树根的ClassLoader加载过,那么在以后整个系统的生命周期内,这个类永远不会被重新加载。

这也是我们经常说的双亲委托机制。

作用

共享功能:一些Framework层级的类一旦被顶层的ClassLoader加载过就缓存在内存里面,以后任何地方用到都不需要重新加载。

隔离功能:不同继承路线上的ClassLoader加载的类肯定不是同一个类,这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。这也好理解,一些系统层级的类会在系统初始化的时候被加载,比如java.lang.String,如果在一个应用里面能够简单地用自定义的String类把这个系统的String类给替换掉,那将会有严重的安全问题。

最后,这也带来了一个新的认知:

之前我们可能觉的: 同一个Class = 相同的 ClassName + PackageName
现在我们应该知道: 同一个Class = 相同的 ClassName + PackageName + ClassLoader

源码分析

大概了解了calssloader之后,我们从源码的角度来看一看classloader的具体实现,首先我们进入classloader的核心方法来ClassLoader#loadclass() 看看

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {        //从缓存中查找是否加载过这个类        Class<?> clazz = findLoadedClass(className);        if (clazz == null) {            ClassNotFoundException suppressed = null;            try {              //缓存中没有,我们调用父类来加载(这里递归上去就会让最顶层的classloader来加载)                clazz = parent.loadClass(className, false);            } catch (ClassNotFoundException e) {                suppressed = e;            }            if (clazz == null) {//父类没有加载                try {                    //这里就是交给自己来加载了                    clazz = findClass(className);                } catch (ClassNotFoundException e) {                    e.addSuppressed(suppressed);                    throw e;                }            }        }        return clazz;    }

从上面的源码,我们也很清楚的知道了什么是双亲委托机制把。。。

了解了双亲委托机制之后,我们再来看看到底是怎么加载类的,从上面源码知道,classloader真正实现加载类的方法是再 findClass() 中的,那么我们去看看 BaseDexClassLoader#findClass()

@Overrideprotected Class<?> findClass(String name) throws                ClassNotFoundException {     List<Throwable> suppressedExceptions = new ArrayList<Throwable>();        //从这里我们看到,最后是交给了pathList的findclass()的       Class c = pathList.findClass(name, suppressedExceptions);            ....//部分代码 略       return c;    }

可以发现这里是调用的pathListfindclass() 来加载的,那么我们再来一探pathList

  public BaseDexClassLoader(String dexPath, File optimizedDirectory,  String libraryPath, ClassLoader parent) {      super(parent);      //很清楚的看到 在`BaseDexClassLoader` 初始化的时候就构造了这么一个对象      this.pathList = new DexPathList(this, dexPath, libraryPathoptimizedDirectory);   }

我们发现原来pathList就是 DexPathList 的实例化对象,我们来瞄一眼这个类的构造函数,来稍微了解下这个对象是做什么的

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {            ...//省略部分代码    //这里都是些初始化和验证的代码,我们主要关注的还是他初始化dexElements的方法     this.dexElements =      makeDexElements(splitDexPath(dexPath),optimizedDirectory,suppressedExceptions);             ...//省略部分代码 }

从上面的代码我们只是找到DexPathList的核心成员变量 dexElements 现在我们来看看DexPathList#makeDexElements 中做了些什么

   /**     * Makes an array of dex/resource path elements, one per element of     * the given array.     */    private static Element[] makeDexElements(ArrayList<File> files, File      optimizedDirectory,ArrayList<IOException> suppressedExceptions) {            //这个就是我们最后返回的Element数组 我们重点的关注对象205        ArrayList<Element> elements = new ArrayList<Element>();206        /*207         * Open all files and load the (direct or contained) dex files208         * up front.加载所有文件209         */210        for (File file : files) {//遍历所有文件211            File zip = null;212            DexFile dex = null;213            String name = file.getName();214215            if (file.isDirectory()) {//如果是个文件夹 那么这里不是我们关系的216                // We support directories for looking up resources.217                // This is only useful for running libcore tests.218                elements.add(new Element(file, true, null, null));219            } else if (file.isFile()){220                if (name.endsWith(DEX_SUFFIX)) {221                    // Raw dex file (not inside a zip/jar).222                    try {                            //这里就是得到我们的dex文件,不再展开讲了,其实就是一                            //个普通file的包装223                        dex = loadDexFile(file, optimizedDirectory);224                    } catch (IOException ex) {225                        System.logE("Unable to load dex file: " + file, ex);226                    }227                } else {228                    zip = file;229230                    try {231                        dex = loadDexFile(file, optimizedDirectory);232                    } catch (IOException suppressed) {233                        /*234                         * IOException might get thrown "legitimately" by                             * the DexFile constructor if235                         * the zip file turns out to be resource-only                              *(that is, no classes.dex file236                         * in it).237                         * Let dex == null and hang on to the exception to                             * add to the tea-leaves for238                         * when findClass returns null.239                         */240                        suppressedExceptions.add(suppressed);241                    }242                }243            } else {244                System.logW("ClassLoader referenced unknown path: " + file);245            }246247            if ((zip != null) || (dex != null)) {                    //添加到Element数组,其实这里整块的操作就是将我们本地的dex文件进行加载248                elements.add(new Element(file, false, zip, dex));249            }250        }251252        return elements.toArray(new Element[elements.size()]);253    }

看到这里,我们基本上也了解了DexPathList 是个什么东西了,他加载了我们本地的 dex 文件,并保存在了他的成员变量 elements 数组中,ok,那么最后我们来看看他的DexPathList#findclass() 看看他是怎么加载一个类的

  public Class findClass(String name, List<Throwable> suppressed) {334        for (Element element : dexElements) {//这里就是遍历加载的dex文件的集合的element数组335            DexFile dex = element.dexFile;336337            if (dex != null) {                    //这里就是真正加载一个类文件了,该方法会调用到底层的c库来进行类的加载                    //在往底层就不在本文的讨论范围了338                Class clazz = dex.loadClassBinaryName(name,                                     definingContext, suppressed);339                if (clazz != null) {340                    return clazz;341                }342            }343        }344        if (dexElementsSuppressedExceptions != null) {345            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));346        }347        return null;348    }

那么我们继续追下去到 DexFile#loadClassBinaryName() 会发现最后是一个native方法来加载一个这个类,再往底层就不在本文讨论范围之内了,到这里,我们的类才最终被我们加载出来了,本文也就差不多结束了,并没有什么很难懂的地方,本文也主要是为了更深入的理解Android和为当下比较热门的热修复和插件化打一点基础

原创粉丝点击