美文网首页我爱编程
Android类加载机制简析

Android类加载机制简析

作者: Joe_blake | 来源:发表于2018-06-21 23:48 被阅读40次

    相关Android源码均基于API 26。

    一、Class类简介

    ​ Class是Java程序在运行时,系统对所有的对象进行所谓的运行时类型标识。它记录了每个对象所属的类。虚拟机使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。

    ​ Class 没有公共构造方法。Class 对象是在加载类时由Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

    ​ 虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。

    ​ 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。

    ​ 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

    二、JVM平台提供的三层classLoader

    1. Bootstrap classLoader:采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.、java.uti.等。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
    2. ExtClassLoader:扩展的class loader,加载位于$JAVA_HOME/jre/lib/ext目录下的扩展jar。
    3. AppClassLoader:系统class loader,父类是ExtClassLoader,加载$CLASSPATH下的目录和jar;它负责加载应用程序主函数类。

    三、Android类加载器种类&分析

    Android中有5种类加载器:

    • BootClassLoader

    • URLClassLoader

    • BaseDexClassLoader-----DexClassLoader、PathClassLoader、InMemoryDexClassLoader(O新增)

    1. ClassLoader

    ClassLoader是所有类加载器的父类,是一个抽象类。

    • ClassLoader有三个构造方法,均是private/protected修饰的,如下:

      private ClassLoader(Void unused, ClassLoader parent) {
          this.parent = parent;
      }
      
      protected ClassLoader(ClassLoader parent) {
          this(checkCreateClassLoader(), parent);
      }
      
      //getSystemClassLoader()最终返回的是一个PathClassLoader
      protected ClassLoader() {
          this(checkCreateClassLoader(), getSystemClassLoader());
      }
      public static ClassLoader getSystemClassLoader() {
          return SystemClassLoader.loader;
      }
      

      SystemClassLoader是ClassLoader的一个内部类:

      static private class SystemClassLoader {
              public static ClassLoader loader = ClassLoader.createSystemClassLoader();
      }
      
      private static ClassLoader createSystemClassLoader() {
          String classPath = System.getProperty("java.class.path", ".");
          String librarySearchPath = System.getProperty("java.library.path", "");
      
          // String[] paths = classPath.split(":");
          // URL[] urls = new URL[paths.length];
          // for (int i = 0; i < paths.length; i++) {
          // try {
          // urls[i] = new URL("file://" + paths[i]);
          // }
          // catch (Exception ex) {
          // ex.printStackTrace();
          // }
          // }
          //
          // return new java.net.URLClassLoader(urls, null);
      
          // TODO Make this a java.net.URLClassLoader once we have those?
          return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
      }
      

      ​ 也就是说,当我们创建一个ClassLoader是,要么显示的指定他的parent加载器,否则会自动创建一PathClassLoader作为parent。

      ​ 这里SystemClassLoader是一个典型的使用静态内部类实现的单例模式。

      在Java中,一个ClassLoader创建时如果没有指定parent,那么它的parent默认是AppClassLoader,AppClassLoader继承自URLClassLoader。

    • ClassLoader的loadClass方法如下:

        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        c = findClass(name);
                    }
                }
                return c;
        }
    

    ClassLoader的loadClass方法表现了类加载的基本流程:

    1. findLoadedClass方法检查是否已经加载过这个类;
    2. 如果没有,调用parent的loadClass方法加载该类,如果parent为null,调用findBootstrapClassOrNull方法加载;
    3. 调用parent的loadClass方法后,该class仍为空,则调用findClass方法查找该类(在ClassLoader中发方法抛出ClassNotFoundException异常)。
    这也就是我们常说的 “双亲委派模型”———先向上委托父加载器加载class,找不到再向下返回在自己的类路径下查找并加载目标类

    2. BootClassLoader

    ​ BootClassLoader是ClassLoader的内部类,无修饰符修饰(default)。所以我们无法使用BootClassLoader,也不能使用BootClassLoader动态加载类。

    • BootClassLoader的构造方法中传参为null,结合父类ClassLoader的构造方法,我们可以看到BootClassLoader的parent为null,即BootClassLoader是Android最顶级的ClassLoader。
    public BootClassLoader() {
        super(null);
    }
    
    • BootClassLoader的loadClass方法如下:
        @Override
        protected Class<?> loadClass(String className, boolean resolve)
               throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);
    
            if (clazz == null) {
                clazz = findClass(className);
            }
    
            return clazz;
        }
    
    • BootClassLoader的findClass方法使用Class.classForName实现查找类:
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }
    

    ​ Class.forName(className)是我们利用反射创建一个类常用的方法,内部实际调用的方法是 Class.forName(className,true,classloader);​第2个boolean参数表示类是否需要初始化, 第三个参数是加载这个类的加载器。

    3. URLClassLoader

    ​ URLClassLoader并不是直接继承ClassLoader,而是继承自SecureClassLoader类。

    ​ URLClassLoader只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。在Java开发中,我们可以利用URLClassLoader读取Jar包并反射类。

    4. BaseDexClassLoader

    ​ BaseDexClassLoader继承自ClassLoader,是DexClassLoader、PathClassLoader、InMemoryDexClassLoader的父类,用于加载各种dex中的类,它的两个构造函数如下:

        //access文件
        public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
            // TODO We should support giving this a library search path maybe.
            super(parent);
            this.pathList = new DexPathList(this, dexFiles);
        }
    
        //access path
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
    
            if (reporter != null) {
                reporter.report(this.pathList.getDexPaths());
            }
        }
    

    它的四个参数:

    • dexPath:指目标类所在的路径(可以加载APK、DEX和JAR,也可以从SD卡进行加载)。如果包含多个路径,则需要用System.getProperty("path.separtor")返回的 分隔符分隔。

    • optimizedDirectory:该文件是dexPath路径中的文件解压、优化生成ODEX文件的文件,ClassLoader只能加载内部存储路径中的dex文件,所以该文件路径必须为内部路径。

    • librarySearchPath:指目标类中所使用的C/C++库存放的路径。

    • parent:该加载器的parent。

    BaseDexClassLoader的findClass方法,其主要通过DexPathList.findClasss实现:

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
            Class c = pathList.findClass(name, suppressedExceptions);
            if (c == null) {
                ClassNotFoundException cnfe = new ClassNotFoundException(
                        "Didn't find class \"" + name + "\" on path: " + pathList);
                for (Throwable t : suppressedExceptions) {
                    cnfe.addSuppressed(t);
                }
                throw cnfe;
            }
            return c;
        }
    

    ​ 在构造函数和findClass方法中,都涉及到一个关键类DexPathList。在DexPathList的实现代码内部,会根据类加载器加载路径查找dex文件,然后将它们解析成Element对象,Element对象代表的是dex文件或资源文件,它保存了文件对象。

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

        public DexPathList(ClassLoader definingContext, String dexPath,
                String libraryPath, File optimizedDirectory) {
            // 当前类加载器的父类加载器
            this.definingContext = definingContext;
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            // 根据输入的dexPath创建dex元素对象
            this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                               suppressedExceptions);
            if (suppressedExceptions.size() > 0) {
                this.dexElementsSuppressedExceptions =
                    suppressedExceptions.toArray(newIOException[suppressedExceptions.size()]);
            } else {
                dexElementsSuppressedExceptions = null;
            }
            this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
        }
    

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

    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
                List<IOException> suppressedExceptions, ClassLoader loader) {
          Element[] elements = new Element[files.size()];
          int elementsPos = 0;
          /*
           * Open all files and load the (direct or contained) dex files up front.
           */
          for (File file : files) {
              if (file.isDirectory()) {
                  // We support directories for looking up resources. Looking up resources in
                  // directories is useful for running libcore tests.
                  elements[elementsPos++] = new Element(file);
              } else if (file.isFile()) {
                  String name = file.getName();
    
                  if (name.endsWith(DEX_SUFFIX)) {
                      // Raw dex file (not inside a zip/jar).
                      try {
                          DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                          if (dex != null) {
                              elements[elementsPos++] = new Element(dex, null);
                          }
                      } catch (IOException suppressed) {
                          System.logE("Unable to load dex file: " + file, suppressed);
                          suppressedExceptions.add(suppressed);
                      }
                  } else {
                      DexFile dex = null;
                      try {
                          dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      } catch (IOException suppressed) {
                          /*
                           * IOException might get thrown "legitimately" by the DexFile constructor if
                           * the zip file turns out to be resource-only (that is, no classes.dex file
                           * in it).
                           * Let dex == null and hang on to the exception to add to the tea-leaves for
                           * when findClass returns null.
                           */
                          suppressedExceptions.add(suppressed);
                      }
    
                      if (dex == null) {
                          elements[elementsPos++] = new Element(file);
                      } else {
                          elements[elementsPos++] = new Element(dex, file);
                      }
                  }
              } else {
                  System.logW("ClassLoader referenced unknown path: " + file);
              }
          }
          //裁剪多余长度
          if (elementsPos != elements.length) {
              elements = Arrays.copyOf(elements, elementsPos);
          }
          return elements;
        }
    

    DexPathList.findClasss两参方法如下:

        public Class<?> findClass(String name, List<Throwable> suppressed) {
            // 遍历从dexPath查询到的dex和资源Element
            for (Element element : dexElements) {
                Class<?> clazz = element.findClass(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
    
            if (dexElementsSuppressedExceptions != null) {
                suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
            }
            return null;
        }
    

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

    public Class<?> findClass(String name, ClassLoader definingContext,
                   List<Throwable> suppressed) {
                // 使用DexFile.loadClassBinaryName加载类
                return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;
            }
    

    Element是一个静态内部类,集合了对dex的一些操作

    • 总结---BaseDexClassLoader在实现加载类的流程如下:

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

    5. DexClassLoader

    DexClassLoader继承了BaseDexClassLoader,只有一个构造方法,如下所示:

        /**
       * A class loader that loads classes from {@code .jar} and {@code .apk} files
       * containing a {@code classes.dex} entry. This can be used to execute code not
       * installed as part of an application.
       *
       * <p>This class loader requires an application-private, writable directory to
       * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
       * such a directory: <pre>   {@code
       *   File dexOutputDir = context.getCodeCacheDir();
       * }</pre>
       *
       * <p><strong>Do not cache optimized classes on external storage.</strong>
       * External storage does not provide access controls necessary to protect your
       * application from code injection attacks.
       */
       public class DexClassLoader extends BaseDexClassLoader {
            public DexClassLoader(String dexPath, String optimizedDirectory,
                    String librarySearchPath, ClassLoader parent) {
                super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
            }
        }
    

    所以,DexClassLoader只是做了简单封装,还是基于BaseDexClassLoader实现加载的。

    6. PathClassLoader

    官方解释入下:

    /**
     * Provides a simple {@link ClassLoader} implementation that operates on a list
     * of files and directories in the local file system, but does not attempt to
     * load classes from the network. Android uses this class for its system class
     * loader and for its application class loader(s).
     */
    

    PathClassLoader同样也是对BaseDexClassLoader的简单封装,通过文件list或者文件夹加载class,但是不允许通过网络加载。它有两个构造方法:

    参数含义:

    • dexPath:dex路径
    • librarySearchPath:c、c++库路径
    • parent:父加载器
        public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    

    ​ 不设置librarySearchPath的构造方法,此时默认路径为/data/dalvik-cache。

        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
    

    PathClassLoader在Dalvik虚拟机上只能加载已安装的apk,而在ART上没有此限制。

    7. InMemoryDexClassLoader

    官方解释如下:

    21/**
    22 * A {@link ClassLoader} implementation that loads classes from a
    23 * buffer containing a DEX file. This can be used to execute code that
    24 * has not been written to the local file system.
    25 */
    

    InMemoryDexClassLoader是Android O新增的一个类加载器,也是继承自BaseDexClassLoader。它提供了从内存中的dex文件加载class的能力。

    构造函数如下:

        //Create an in-memory DEX class loader with the given dex buffers.
        public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
            super(dexBuffers, parent);
        }
    

    该方法通过传入一个ByteBuffer[],加载内存中的dex文件中的类。

    另一个构造方法是传入一个ByteBuffer,大同小异:

        public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
            this(new ByteBuffer[] { dexBuffer }, parent);
        }
    

    相关文章

      网友评论

        本文标题:Android类加载机制简析

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