admin 管理员组

文章数量: 887006

java的classloader 和android 的classloader 以及android classloader类加载机制

java的classloader 和android 的classloader
是有一定区别的,java的classloader 加载的class文件
android 的classloader 加载的是dex文件

java classloader

类加载器类型
有以下几种。每种类型负责的加载的类都不一样。各自负责各自的。他们关系是从父类继承关系。从上到下分别是 :

Bootstrap ClassLoader(启动类加载器):该类加载器由C++实现的。负责加载Java基础类,对应加载的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等。

Extension ClassLoader(标准扩展类加载器):继承URLClassLoader。对应加载的文件是%JRE_HOME/lib/ext 目录下的jar和class等。

App ClassLoader(系统类加载器):继承URLClassLoader。对应加载的应用程序classpath目录下的所有jar和class等。

还有一种就是我们自定义的 ClassLoader,由Java实现。我们可以自定义类加载器,并可以指定这个类加载器 要 加载哪个路径下的class文件

类加载机制靠的是 双亲委派机制

最开始都是 利用classloader.loadclass(classname) loadclass 加载一个类 我们看看这方法源码

public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// First, check if the class has already been loadedClass 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);}}if (resolve) {resolveClass(c);}return c;
}

在这个classloader的 loadClass 方法 一开始 , 调用 findLoadedClass(name); 第一步先检查 自己有没有加载过。
如果没有递归 调用 父类的 parent.loadClass(name, false) ,在父类的的 loadClass 继续调用 findLoadedClass(name); 检查 自己有没有加载过
如果父类加载过就返回class实例。如果没有就再继续父类的 loadClass。
直到没有父类,就会调用
c = findBootstrapClassOrNull(name); 即 使用启动类加载器作为父类加载器。如果父类启动器没有加载过就去加载,如果判断不是自己加载的范围 ,就调用 c = findClass(name); 由子类去加载。子类 判断不是自己加载就再交给他子类加载。直到 最后一个子类的 findClass 去加载。

意思就是,当一个 类加载器 要 加载一个类的时候,先 看下自己有没有 加载过 ,没有 就不断 一层一层向上去 询问 的 有没有人加载过 ,直到问到他的祖宗类加载器 如果这个询问过程有人加载过了 ,那么询问过程就结束了。 这个类也不会再被加载了,都加载过了。 然后如果大家都没加载过。祖宗类就会去判断是不是他负责加载的类 。是就让他去加载 。如果不是i他负责的,就把 事情交给他的子类,是不是他负责的。是就去加载,不是继续给子类。这样又向下 一层层的 来到最底层的子类 去加载,如果都没人负责加载。会抛出异常。

双亲委派的作用 好处就是
① 避免重复加载,当父容器已经加载过了就没必要在加载一遍 ,防止加载同一个.class。通过委托去询问上级是否已经加载过该.class,如果加载过了,则不需要重新加载。保证了数据安全

②安全性考虑,防止核心类APi被篡改,通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。只有 同一个类加载器加载的 同一个 class 文件才是同一个对象 。 这样则保证了Class的执行安全。

我们试想一下,如果不使 用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式, 就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String

为什么要自定义一个类加载器,如何自定义
自定类加载器是为了加载 指定 的类文件 路径 ,上面的已有的类加载器只负责他加载的类范围。
如何自定义呢?

先看下 Android5.1 源码中 ClassLoader 的 loadClass 方法

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {Class<?> clazz = findLoadedClass(className);if (clazz == null) {ClassNotFoundException suppressed = null;try {//先让父类加载器加载clazz = parent.loadClass(className, false);} catch (ClassNotFoundException e) {suppressed = e;}//当所有父类节点的类加载器都没有找到该类时,当前加载器调用findClass方法加载。if (clazz == null) {try {clazz = findClass(className);} catch (ClassNotFoundException e) {e.addSuppressed(suppressed);throw e;}}}

也就是 如果上面所有的类加载器都没有加载也不是他们负责 那就这个类加载器自己去 加载调用方法就是 findClass 方法
如果我们只要继承 classloader 然后重写 findClass 就可以了 。

package com.lordx.sprintbootdemo.classloader;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;/*** 自定义ClassLoader* 功能:可自定义class文件的扫描路径*/
// 继承ClassLoader,获取基础功能
public class TestClassLoader extends ClassLoader {// 自定义的class扫描路径private String classPath;public TestClassLoader(String classPath) {this.classPath = classPath;}// 覆写ClassLoader的findClass方法protected Class<?> findClass(String name) throws ClassNotFoundException {// getDate方法会根据自定义的路径扫描class,并返回class的字节byte[] classData = getDate(name);if (classData == null) {throw new ClassNotFoundException();} else {// 生成class实例return defineClass(name, classData, 0, classData.length);}}private byte[] getDate(String name) {// 拼接目标class文件路径String path = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";try {InputStream is = new FileInputStream(path);ByteArrayOutputStream stream = new ByteArrayOutputStream();byte[] buffer = new byte[2048];int num = 0;while ((num = is.read(buffer)) != -1) {stream.write(buffer, 0 ,num);}return stream.toByteArray();} catch (Exception e) {e.printStackTrace();}return null;}
}

使用自定义的类加载器

package com.lordx.sprintbootdemo.classloader;public class MyClassLoader {public static void main(String[] args) throws ClassNotFoundException {// 自定义class类路径String classPath = "/Users/zhiminxu/developer/classloader";// 自定义的类加载器实现:TestClassLoaderTestClassLoader testClassLoader = new TestClassLoader(classPath);// 通过自定义类加载器加载Class<?> object = testClassLoader.loadClass("ClassLoaderTest");// 这里的打印应该是我们自定义的类加载器:TestClassLoaderSystem.out.println(object.getClassLoader());}
}

Android class loader

BootClassLoader ,这个 BootClassLoader 就是用来加载 sdk framewrok层系统层里面的类,注意,只是 framewrok层系统类。而项目依赖的库里面的类不算事系统类,所以就不是BootClassLoader加载,而是用PathClassLoader。比如
androidx 库里面的类就不是系统sdk类,而是依赖的库类,是用PathClassLoader加载的。

PathCLassLoader ,BaseDexClassLoader的子类,是整个程序中的类加载器,相当于 java 中的 AppClassLoader ,
也就是说我们整个项目的除了系统的类是用BootClassLoader加载的 其他都是用 PathCLassLoader加载的 ,也就是说,根据 双亲委派 。 PathCLassLoader去加载类的时候 先判断 BootClassLoader 是否加载过,没有那就是 自己去加载了 也就是 PathCLassLoader自己去加载。
注意,我们在项目中,使用一个类的时候,并不会 去用 CLassLoader .loadClass 去加载一个类,然后在去反射去调用这个类的信息。实际上,内部上是系统已经帮我们 用PathCLassLoader loadClass 帮我们实现了。

DexClassLoader BaseDexClassLoader的子类, 相当于 java 的CustomClassLoader。

android 8.0 之前
PathClassLoader和DexClassLoader的区别

BaseDexClassLoader PathClassLoader和DexClassLoader 构造函数分别是

public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory,String libraryPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), libraryPath, parent);}
}public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
}

dexPath:指定的是dex文件地址,多个地址可以用":"进行分隔
optimizedDirectory:指定 dex 经过 JIT 优化后的 odex 文件的存储路径
libraryPath :动态库路径(将被添加到 app 动态库搜索路径列表中)可以为null
parent :制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。

可以看出 PathClassLoader 和 DexClassLoader 的区别就在于构造函数中多了这个 optimizedDirectory 这个参数。
PathClassLoader 中 optimizedDirectory 为 null,DexClassLoader 中需要 new File(optimizedDirectory)去存储dex其实是odex(因为会经过JIT即使编译优化成odex文件)

apk安装之后的dex文件默认会放在默认存放在 /data/dalvik-cache目录下,而PathClassLoader没有这个 optimizedDirectory文件是因为,PathClassLoader的optimizedDirectory 默认值已经是 /data/dalvik-cache,所以PathClassLoader才只能加载内部的dex。

而 DexClassLoader 则需要 指定创建这个文件 optimizedDirectory ,这样外部dex 经过 JIT 即使编译后的odex 文件才有地方存放,而你这个DexClassLoader 是从你的 optimizedDirectory 去查找dex资源的。

接着在android 8.0之后,DexClassLoader的构造函数虽然还有这个optimizedDirectory,但是你这个参数怎么它都不管内部直接传入null

 public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}

所以在android 8.0之后 ,DexClassLoader和PathClassLoader就没有区别了。

注意下 optimizedDirectory路径不要随便写个路径进去,最好放在私有目录

context.getCashDir().getAbsolutePath();

Android class loader 类加载机制

Android class loader 也是采用的双亲委派机制 当我们调用classloader.loadClass 的时候 先是 根据双亲委派去查需要哪个类加载器
去findclass

 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {c = parent.loadClass(name, false);//1} else {c = findBootstrapClassOrNull(name);//2}}catch (ClassNotFoundException e){}if (c == null) {c = findClass(name);//3}}return c;}
@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();//在自己的成员变量DexPathList中寻找 ,返回 要查找的类 ,找不到抛异常//也就是说 这个classLoader 负责加载的类 都是在这个 dexpathlist中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;}

DexPathList 的 findClass 方法

public Class findClass(String name, List<Throwable> suppressed) {//循环便利成员变量dexElements,//查找所有负责每个elements.dexFile  调用DexFile.loadClassBinaryName 去根据类名查找要找的class//如果找不到就返回nullfor (Element element : dexElements) {DexFile dex = element.dexFile;if (dex != null) {Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);if (clazz != null) {return clazz;}}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}

通过以上代码我们可以看出,

每个classloader 对象 都有各自负责加载对应的 类,
classloader 内部有成员 Dexpathlist类的一个 对象dexpathlist ,dexpthlist 有 个 Element[] elements 数组,内部有n个Element,Element类有个变量dexFile ,而这个dexFile就是代表每一个 dex 文件。

所以 classloader findclass 找类 就是去 dexpathlist 对象里面的 elements 数组里面找

而一开始 存储数据的时候,classloader就会调用 Dexpathlist 里面的makeElements()这个方法 把 dex文件 变成Element 类 ,多个dex 文件 List dex 就会变成 Element[] elements 数组。

//android 7
#DexPathList.java   makeElements
private static Element[] makeElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions,boolean ignoreDexFiles,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) {File zip = null;File dir = new File("");DexFile dex = null;String path = file.getPath();String name = file.getName();if (path.contains(zipSeparator)) {String split[] = path.split(zipSeparator, 2);zip = new File(split[0]);dir = new File(split[1]);} else if (file.isDirectory()) {// We support directories for looking up resources and native libraries.// Looking up resources in directories is useful for running libcore tests.elements[elementsPos++] = new Element(file, true, null, null);} else if (file.isFile()) {if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {// Raw dex file (not inside a zip/jar).try {dex = loadDexFile(file, optimizedDirectory, loader, elements);} catch (IOException suppressed) {System.logE("Unable to load dex file: " + file, suppressed);suppressedExceptions.add(suppressed);}} else {zip = file;if (!ignoreDexFiles) {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);}}}} else {System.logW("ClassLoader referenced unknown path: " + file);}if ((zip != null) || (dex != null)) {elements[elementsPos++] = new Element(dir, false, zip, dex);}}if (elementsPos != elements.length) {elements = Arrays.copyOf(elements, elementsPos);}return elements;}

所以
classloader findclass 去加载类的时候实际流程是:

classloader.loadclass> classloader findclass >dexpathlist finclass >遍历elements数组的每一个element的dexFilie去调用>
dexFile loadclassBinaryName()>defindclass()>defindNative(),它是个Native 方法,最终到Native层寻找。

本文标签: java的classloader 和android 的classloader 以及android classloader类加载机制