Android 中的类加载器

ClassLoader 的类型

  • java 中的ClassLoader 加载的是jar 和class文件
  • Android 中的ClassLoader 加载的是dex文件

两种ClassLoader类型

  • 系统类加载器
  • 自定义加载器
系统类加载器 (3种)
  • BootClassLoader
  • PathClassLoader
  • DexClassLoader
BootClassLoader
  • Android 系统启动时会使用BootClassLoader来预加载常用类,而BootClassLoader是由java实现的
  • java 中的BootstrapClassLoader 是由C/C++代码实现的
BootClassLoader 代码
class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    ......
  • BootClassLoader 是 ClassLoader 的内部类
  • BootClassLoader 继承自ClassLoader
  • BootClassLoader是个单例类
  • BootClassLoader 的访问修饰符是默认的,只有在同一个包中才可以访问,
DexClassLoader
用途
  • DexClassLoader 可以加载dex文件
  • DexClassLoader也可以加载包含dex的压缩文件(apk 和jar文件)
DexClassLoader源码如下
public class DexClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     *     should be written; must not be {@code null}
     * @param libraryPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
    *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

DexClassLoader继承自BaseDexClassLoader,方法都在BaseDexClassLoader中实现

DexClassLoader 的构造方法有如下4个参数

  • dexPath:dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"
  • optimizedDirectory:解压的dex文件存储路径,这个路径必须是一个内部存储路径:如/data/data//…
  • librarySearchPath:包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以为null
  • parent:父加载器
PathClassLoader
用途:
  • PathClassLoader 用来加载系统类和应用程序的类
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).
 */
public class PathClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code PathClassLoader} that operates on a given list of files
     * and directories. This method is equivalent to calling
     * {@link #PathClassLoader(String, String, ClassLoader)} with a
    * {@code null} value for the second argument (see description there).
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    /**
     * Creates a {@code PathClassLoader} that operates on two given
     * lists of files and directories. The entries of the first list
     * should be one of the following:
    *
    * <ul>
     * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
     * well as arbitrary resources.
     * <li>Raw ".dex" files (not inside a zip file).
     * </ul>
    *
     * The entries of the second list should be directories containing
     * native library files.
     *
     * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}
  • PathClassLoader 继承自BaseDexClassLoader,也都在BaseDexClassLoader中实现
  • 在PathClassLoader的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的值为/data/dalvik-cache
  • PathClassLoader无法自定义解压dex文件存储路径
  • PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)

ClassLoader 的继承关系

先看图再解释
在这里插入图片描述

  • ClassLoader 是一个抽象类,其中定义了ClassLoader的主要功能,BootClassLoader是它的内部类
  • SecureClassLoader 类和JDK8中的SecureClassLoaderL类的代码是一样的,它集成了抽象类ClassLoader,SecureClassLoader 并不是ClassLoader 的实现类,而是扩展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性
  • URLClassLoader 类和JDK8 中的URLClassLoader类代码是一样的,它继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源
  • InMemoryDexClassLoader 是Android 8.0 新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件
  • BaseDexClassLoader 继承自ClassLoader ,是抽象类ClassLoader的具体实现类,PathClassLoader,DexClassLoader和InMemoryDexClassLoader 都继承自它

ClassLoader 的加载过程

  • Android 的ClassLoader 同样遵循双亲委托模式
  • ClassLoader的加载方法为 loadClass
  • loadClass方法被定义在抽象类ClassLoader中
ClassLoader -> loadClass 方法源码
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded 首先,检查类是否已经加载
            Class c = findLoadedClass(name);//1
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//2
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);//3
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {//4
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);//5

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

以下对上面代码的说明

  • 注释1:检查传入的类是否已经加载,如果已经加载就返回该类
  • 注释2:判断父加载器是否存在,存在就调用父加载器的loadClass方法,如果不存在就调用注释3处的findBootstrapClassOrNull 方法,这个方法会直接返回null
  • 如果注解4处代码成立,说明向上委托流程没有检查出类已经被加载,就会执行注释5处的findClass方法来进行查找流程

下面为注释5处的findClass 方法

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

上面的findClass方法中直接抛出异常, 说明需要子类实现,下面为BaseDexClassLoader的代码

public class BaseDexClassLoader extends ClassLoader {
    // 需要加载的dex列表
    private final DexPathList pathList;
    // dexPath要加载的dex文件所在的路径,optimizedDirectory是odex将dexPath
    // 处dex优化后输出到的路径,这个路径必须是手机内部路劲,libraryPath是需要
    // 加载的C/C++库路径,parent是父类加载器对象
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

       if (reporter != null) {            
            reportClassLoaderChain();
       }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        // 使用pathList对象查找name类
        Class c = pathList.findClass(name, suppressedExceptions);//1
        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;
    }
}
  • 在注释 1 的地方调用了 DexPathList 的findClass方法

下面看下DexPathList 中的 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;
    }
  • 注释1处遍历Element 数组dexElements
  • 注释2处调用Element的findClass方法
  • Element是 DexPathList 的静态内部类
Element 源码如下
/*package*/ static class Element {
        /**
         * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
         * (only when dexFile is null).
         */
        private final File path;

        private final DexFile dexFile;

        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

        /**
         * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
         * should be null), or a jar (in which case dexZipPath should denote the zip file).
         */
        public Element(DexFile dexFile, File dexZipPath) {
            this.dexFile = dexFile;
            this.path = dexZipPath;
        }

        public Element(DexFile dexFile) {
            this.dexFile = dexFile;
           this.path = null;
       }

        public Element(File path) {
          this.path = path;
          this.dexFile = null;
        }
    .........
    public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;//1
        }
  • 从Element 的构造方法可以看出,其内部封装了DexFile,它用于加载dex
  • 在注释1处如果DexFile不为null就调用 DexFile 的loadClassBinaryName 方法
DexFile 中 loadClassBinaryName 方法源码如下
 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);//1
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
  • 在loadClassBinaryName 方法中调用了defineClass方法
  • 注解1处调用了defineClassNative方法来加载dex相关文件,这个方法是Native方法

到此ClassLoader 的查找流程完成,可以概括为下图
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章