理解 ClassLoader

    技术2024-01-22  123

    热修复和插件化是目前比较热门的技术,要想更好地掌握它们需要先了解ClassLoader关于ClassLoader,可能有的同学会认为Java中的ClassLoaderAndroid中的ClassLoader 没有区别,在第11章中我们知道DVMART加载的是dex文件,而JVM加载的是Class 文件,因此它们的类加载器ClassLoader肯定是有区别的。这一章分别介绍Java中的 ClassLoaderAndroid中的ClassLoader,这样它们的区别也就一目了然了。

    Java 中的 ClassLoader

    10.2.3节中提到过类加载子系统,它的主要作用就是通过多种类加载器(ClassLoader) 来查找和加载Class文件到Java虚拟机中。

    ClassLoader 的类型

    Java中的类加载器主要有两种类型,即系统类加载器和自定义类加载器。其中系统类加载器包括 3 种,分别是 Bootstrap ClassLoader, Extensions ClassLoader Application ClassLoader

    1.Bootstrap ClassLoader (引导类加载器)

    C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang., java.uti. 等这些系统类。它用来加载以下目录中的类库:

    $JAVA_HOME/jre/lib 目录。-Xbootclasspath参数指定的目录。

    Java虚拟机的启动就是通过Bootstrap ClassLoader创建一个初始类来完成的。由于 Bootstrap ClassLoader是使用C/C++语言实现的,所以该加载器不能被Java代码访问到。 需要注意的是,Bootstrap ClassLoader并不继承java.lang.ClassLoader0我们可以通过如下代 码来得出Bootstrap ClassLoader所加载的目录:

    public class ClassLoaderTest { public static void main(String[]args) { System.out.println(System.getProperty("sun.boot.class.path")); } }

    打印结果为:

    C:\Program Files\Java\jdkl.8.0_102\j re\lib\resources.jar;C:\Program Files'Java' jdk1.8.0_102\jre\lib\rt.jar; C:\Program Files\Java\jdkl.8.0_102\jre\lib\sunrsasign.jar; C:\Program Files\Java\jdkl.8.0_102\jre\lib\jsse.jar;C:\Program Files\Java\jdkl.8.0_102\ jre\lib\jce.jar; C:\Program Files\Java\jdkl.8.0_102\j re\lib\charsets.jar; C:\Program Files'Java'jdkl.8.0_102\jre\lib\jfr.jar;C:\Program Files\Java\jdkl.8.0_102\jre\classes

    可以发现几乎都是$JAVA_HOME/jre/lib目录中的jar包,包括rt.jarresources.jarcharsets.jar 等。

    2.Extensions ClassLoader (拓展类加载器)

    Java中的实现类为ExtClassLoader,因此可以简称为ExtClassLoader,它用于加载Java 的拓展类,提供除了系统类之外的额外功能。ExtClassLoader用来加载以下目录中的类库:

    加载$JAVA_HOME/jre/lib/ext 目录。系统属性java.ext.dir所指定的目录。

    通过以下代码可以得到Extensions ClassLoader加载目录:

    System.out.printin(System.getProperty("java.ext.dirs));

    打印结果为:

    C:\Program Files\Java\jdkl.8.0_102\jre\lib\ext; C:\Windows\Sun\Java\lib\ext

    3.Application ClassLoader (应用程序类加载器)

    Java中的实现类为AppClassLoader,因此可以简称为AppClassLoader,同时它又可以称作System ClassLoader (系统类加载器),这是因为AppClassLoader可以通过ClassLoader getSystemClassLoader方法获取到。它用来加载以下目录中的类库:

    当前程序的Classpath目录。系统属性java.class.path指定的目录。

    4.Custom ClassLoader (自定义类加载器)

    除了系统提供的类加载器,还可以自定义类加载器,自定义类加载器通过继承 java.lang.ClassLoader 类的方式来实现自己的类加载器,Extensions ClassLoader App ClassLoader也继承了 java.lang.ClassLoader类。关于自定义类加载器后面会进行介绍。

    ClassLoader 的继承关系

    运行一个Java程序需要用到几种类型的类加载器呢?如下所示。

    public class ClassLoaderTest ( public static void main(String[] args) { ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (loader != null) { System.out.printin(loader);//1 loader = loader.getParent(); } } }

    我们得到当前类ClassLoaderTest的类加载器,并在注释1处打印出来,紧接着打印出当前类的类加载器的父加载器,直到没有父加载器时就终止循环,打印结果如下所示:

    sun.misc.Launcher$AppClassLoader@75b84c92

    sun.misc.Launcher$ExtClassLoader@lb6d3586

    1行说明加载ClassLoaderTest的类加载器是AppClassLoader,2行说明 AppClassLoader的父加载器为ExtClassLoader至于为何没有打印出ExtClassLoader的父加载器 Bootstrap ClassLoader,这是因为 Bootstrap ClassLoader 是由 C/C++编写的,并不是一 个Java类,因此我们无法在Java代码中获取它的引用。系统所提供的类加载器有3种类型, 但是系统提供的ClassLoader却不只有3个。另外,AppClassLoader的父类加载器为ExtClassLoader,并不代表 AppClassLoader 继承自 ExtClassLoader, ClassLoader 的继承关系如图12.1所示。

    可以看到图12-1中共有5ClassLoader相关类,下面简单对它们进行介绍。

    ClassLoader是一个抽象类,其中定义了 ClassLoader的主要功能。SecureClassLoader 继承了 抽象类 ClassLoader,SecureClassLoader 并不是 ClassLoader的实现类,而是拓展了 ClassLoader类加入了权限方面的功能,加强了 ClassLoader的安全性。URLClassLoader继承自SecureClassLoader,可以通过URL路径从jar文件和文件夹 中加载类和资源。ExtClassLoader AppClassLoader 都继承自 URLClassLoader,它们都是 Launcher 的 内部类,Launcher Java 虚拟机的入口 应用,ExtClassLoader AppClassLoader 都是在Launcher中进行初始化的。

    双亲委托模式

    类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行査找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去査找。这样讲可能会有些抽象,来看图12-2

                                                                                                   图12-2双亲委托模式

    我们知道类加载子系统用来查找和加载Class文件到Java虚拟机中,假设我们要加载一个位于D盘的Class文件,这时系统所提供的类加载器不能满足条件,这时就需要我们自定义类加载器继承自java.lang.ClassLoader,并复写它的findClass方法。加载D盘的Class 文件步骤如下:

    自定义类加载器首先从缓存中查找Class文件是否已经加载,如果已经加载就返回该Class,如果没加载则委托给父加载器也就是AppClassLoader按照图12-2中虚线的方向递归步骤1。一直委托到 Bootstrap ClassLoader,如果 Bootstrap ClassLoader 查找缓存也没有加载Class文件,则在$JAVA_HOME/jre/lib目录中或者-Xbootclasspath参数指定的目录中进行查找,如果找到就加载并返回该Class,如果没有找到则交给子加载器ExtClassLoaderExtClassLoader在$JAVA_HOME/jre/lib/ext目录中或者系统属性java.ext.dir所指定的目录中进行查找,如果找到就加载并返回,找不到则交给AppClassLoaderAppClassLoadeClasspath目录中或者系统属性java.class.path指定的目录中进行查找,如果找到就加裁并返回,找不到交给我们自定义的类加载器,如果还找不到则抛出异常。

    总的来说就是Class文件加载到类加载子系统后,先沿着图12-2中虚线的方向自下而上进行委托,再沿着实线的方向自上而下进行查找和加载,整个过程就是先上后下。结合 12.1.2节中讲的ClassLoader的继承关系,可以得出ClassLoader的父子关系并不是使用继 承来实现的,而是使用组合来实现代码复用的。

    类加载的步骤在JDK8的源码中也得到了体现,下面来査看抽象类ClassLoaderloadClass 方法:

     

    protected Class<?> More ...loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name);//l if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(namez false);//2 } else { c = findBootstrapClassOrNull(name);//3 } } catch (ClassNotFoundException e) { } if (c == null) { long tl = System.nanoTime(); c = findClass(name);//4 sun.misc.PerfCounter.getParentDelegationTime().addTime(tl - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(tl); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveclass(c); } return c; } }

    在注释1处用来检査传入的类是否已经加载,如果已经加载则后面的代码不会执行, 最后会返回该加载类。没有加载会接着向下执行,在注释2处,如果父类加载器不为null, 就调用父类加载器的loadClass方法。如果父类加载器为null就调用注释3处的 findBootstrapClassOrNull 方法,这个方法内部调用了 Native 方法 findBootstrapClass, findBootstrapClass方法中最终会用Bootstrap Classloader来检査该类是否已经加载,如果没有加载就说明向上委托流程中没有加载该类,则调用注释4处的findClass方法继续向下进行査找流程。

    采取双亲委托模式主要有如下两点好处。

    避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是直接读取已经加载的Class更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的 String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java 虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易。

    自定义 ClassLoader

    系统提供的类加载器只能够加载指定目录下的jar包和Class文件,如果想要加载网络 上的或者D盘某一文件中的jar包和Class文件则需要自定义ClassLoader实现自定义 ClassLoader需要如下两个步骤:

    定义一个自定义ClassLoade并继承抽象类ClassLoader复写findClass方法,并在findClass方法中调用defineClass方法。

    下面我们就自定义一个ClassLoader用来加载位于D:\libClass文件。首先编写测试类并生成Class文件,如下所示:

    package com.example; public class Jobs { public void say () { System.out.printin("One more thing"); } }

    将这个Jobs.java放入到D:\lib中,使用cmd命令进入D:\lib目录中,执行Javac Jobs.java 对该java文件进行编译,这时会在D:\lib中生成Jobs.class接下来在AS中创建一个Java Library,编写自定义ClassLoader,如下所示:

     

    import java.io.ByteArrayOutputStream; import java.io.File; import java.io.Fileinputstream; import java.io.lOException; import java.io.InputStream; class DiskClassLoader extends ClassLoader { private String path; public DiskClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; byte[] classData = loadClassData(name);//l if (classData == null) { throw new ClassNotFoundException(); } else { clazz= defineClass(name, classData, 0, classData.length);//2 } return clazz; } private byte[] loadClassData(String name) { String fileName = getFileName(name); File file = new File(path,fileName); Inputstream in=null; ByteArrayOutputStream out=null; try { in = new FilelnputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length=0; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } return out.toByteArray(); } catch (lOException e) { e.printStackTrace(); }finally { try { if(in!=null) { in.close(); } } catch (IOException e) ( e.printStackTrace (); } try{ if(out!=null) { out.close(); } }catch (IOException e)( e.printStackTrace(); } } return null; } private String getFileName(String name) { int index = name.lastlndexOf(*.*); if (index == -1) { //如果没有找到,.,则直接在末尾添加.class return name+,*.class*'; }else{ return name.substring(index+l)+n.class"; } } }

    这段代码有几点需要注意的,注释1处的loadClassData方法会获得class文件的字节码数组,并在注释2处调用defineClass方法将class文件的字节码数组转为Class类的实例。 在loadClassData方法中需要对流进行操作,关闭流的操作要放在finally语句块中,并且要 对inout分别使用try语句,如果inout共同在一个try语句中,假设inclose()发生异常 的话,就无法执行out.close()最后我们来验证DiskClassLoader是否可用,代码如下所示:

     

    import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ClassLoaderTest { public static void main(String!] args) { DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");//l try { Class c = diskClassLoader.loadClass("com.example.Jobs");//2 if (c != null) { try { Object obj = c.newlnstance(); System.out.println(obj.getClass().getClassLoader()); Method method = c.getDeclaredMethod("say", null); method.invoke(obj, null);//3 } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }

    在注释1处创建DiskClassLoader并传入要加载类的路径,在注释2处加载Class文件, 需要注意的是,不要在项目工程中存在名为com.example.JobsJava文件,否则就不会使 用DiskClassLoader来加载,而是使用AppClassLoader来负责加载,这样我们定义的 DiskClassLoader就变得毫无意义。接下来在注释3处通过反射来调用Jobssay方法,打印结果如下:

    com.example.DiskClassLoader@4554617c One more thing

    使用了 DiskClassLoader来加载Class文件,say方法也正确执行,我们的目的就达到了。

    Android 中的 ClassLoader

    12.1节我们学习了 Java中的ClassLoader,有的读者会把AndroidJava中的 ClassLoader 搞混,甚至会认为 Java 中的 ClassLoader Android 中的 ClassLoader 是一样的, 这显然是不对的。这一篇文章我们就来学习Android中的ClassLoader,来查看它和Java中 的ClassLoader有何不同。

    ClassLoader 的类型

    我们知道Java中的ClassLoader可以加载jar文件和Class文件(本质是加载Class文 件),这一点在Android中并不适用,因为无论是DVM还是ART,它们加载的不再是Class 文件,而是dex文件,这就需要重新设计ClassLoader相关类,我们先来学习ClassLoader 的类型。

    Android中的ClassLoader类型和Java中的ClassLoader类型类似,也分为两种类型, 分别是系统类加载器和自定义加载器。其中系统类加载器主要包括3种,分别是 BootClassLoader, PathClassLoader DexClassLoader

    1.BootClassLoader

    Android系统启动时会使用BootClassLoader来预加载常用类,与SDK中的Bootstrap ClassLoader不同,它并不是由C/C++代码实现的,而是由Java实现的,BootClassLoader 的代码如下所示:

    libcore/ojluni/src/main/java/java/lang/ClassLoader.java

    class BootClassLoader extends ClassLoader { private static BootClassLoader instance; @FindBugsSuppressWarnings ("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") public static synchronized BootClassLoader getlnstance() { if (instance == null) ( instance = new BootClassLoader(); } return instance; } }

    BootClassLoader ClassLoader 的内部类并继承自 ClassLoaderBootClassLoader 是 一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才 可以访问,因此我们在应用程序中是无法直接调用的。

    2.DexClassLoader

    DexClassLoader可以加载dex文件以及包含dex的压缩文件(apkjar文件),不管加载哪种文件,最终都要加载dex文件,在这一章为了方便理解和叙述,将dex文件以及包 含dex的压缩文件统称为dex相关文件。查看DexClassLoader的代码,如下所示:

    libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

    public class DexClassLoader extends BaseDexClassLoader ( public DexClassLoader(String dexPath, String optimizedDirectory,String 1ibrarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), 1ibrarySearchPath, parent); } }

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

    dexPathdex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"。optimizedDirectory解压的dex文件存储路径,这个路径必须是一个内部存储路径, 在一般情况下,使用当前应用程序的私有路径:/data/data/<Package Name>/...librarySearchPath包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以 为 nullparent父加载器。

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

    3.PathClassLoader

    Android系统使用PathClassLoader来加载系统类和应用程序的类,下面来查看它的代码:

    libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

    public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){ super(dexPath, null, librarySearchPath, parent); } }

    PathClassLoader 继承自 BaseDexClassLoader,也都在 BaseDexClassLoader 中实现。

    PathClassLoader 的构造方法中没有参数 optimizedDirectory,这是因为 PathClassLoader 已经默认了参数 optimizedDirectory 的值为/data/dalvik-cache,很显然 PathClassLoader 无法 定义解压的dex文件存储路径,因此PathClassLoader通常用来加载已经安装的apkdex 文件(安装的apkdex文件会存储在/data/dalvik-cache中)。

    ClassLoader 的继承关系

    运行一个应用程序需要用到几种类型的类加载器呢?如下所示:

    public class MainActivity extends AppCompatActivity ( @Override protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activity_main); ClassLoader loader = MainActivity.class.getClassLoader(); while (loader != null) ( Log.d("liuwangshu", loader.toString()) ;//l loader = loader.getParent(); } } }

    首先我们得到MaiiiActivity的类加载器,并在注释1处通过Log打印出来,接着通 过循环打印出当前类的类加载器的父加载器,直到没有父加载器终止循环,打印结果如下所示:

    可以看到有两种类加载器,一种是PathClassLoader,另一种则是BootClassLoaderDexPathList 中包含了 很多 apk 的路径,其中 /data/app/com.example.liuwangshu.moonclassloader-2/base.apk就是示例应用安装在手机上的位置。DexPathList是在 BaseDexClassLoader的构造方法中创建的,里面存储了 dex相关文件的路径,在 ClassLoader执行双亲委托模式的査找流程时会从DexPathList中进行查找,在12.2.3节我们还会再提到它。

    除了上面所讲的3种主要的类加载器外,Android还提供了其他的类加载器和

    ClassLoader相关类,ClassLoader的继承关系如图12-3所示。

                                                                    图 12-3 Android8.0 ClassLoader 的继承关系

    可以看到图12-3中一共有8ClassLoader相关类,其中有一些和Java中的ClassLoader 相关类十分类似,下面简单对它们进行介绍:

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

    ClassLoader 的加载过程

    AndroidClassLoader同样遵循了双亲委托模式,ClassLoader的加载方法为loadClass 方法,这个方法被定义在抽象类ClassLoader中,如下所示:

    libcore/ojluni/src/main/java/java/lang/ClassLoader.java

    protected Class<?> loadclass(String name, boolean resolve) throws ClassNotFoundException{ Class<?> c = findLoadedClass(name);//I if (c = null) { try { if (parent != null) {//2 c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name);//3 } } catch (ClassNotFoundException e) { } if (c == null) {//4 c = findClass(name);//5 } return c; }

    ClassLoader loadClass 方法和 12.1.3 节讲的 loadClass 方法(JDK ClassLoader loadClass方法)类似。在注释1处用来检查传入的类是否已经加载,如果已经加载就返回该类,如果没有加载就在注释2处判断父加载器是否存在,存在就调用父加载器的loadClass 方法,如果不存在就调用注释3处的findBootstrapClassOrNull方法,这个方法会直接返回 nullo如果注释4处的代码成立,说明向上委托流程没有检査出类已经被加载,就会执行注 释5处的findClass方法来进行查找流程,findClass方法如下所示:

    libcore/ojluni/src/main/java/java/lang/ClassLoader.java

     

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

    findClass方法中直接抛出了异常,这说明findClass方法需要子类来实现, BaseDexClassLoader的代码如下所示:

    libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

    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()); } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(namez suppressedExceptions);//l if (c = null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \n** + name + ”on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }

    BaseDexClassLoader的构造方法中创建了 DexPathList,在注释1处调用了 DexPathList findClass 方法:

    libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    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处调用ElementfindClass方法, ElementDexPathList的静态内部类:

    libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    static class Element ( private final File path; private final DexFile dexFile; private ClassPathURLStreamHandler urlHandler; private boolean initialized; 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(namez defining Context, suppressed) : null;//1 } }

    Element的构造方法可以看出,其内部封装了 DexFile,它用于加载dex在注释1 处如果 DexFile 不为 null 就调用 DexFile loadClassBinaryName 方法:

    libcore/dalvik/src/main/java/dalvik/system/DexFile.java

    public Class loadClassBinaryName (String name, ClassLoader loader, List<Throwable> suppressed) ( return defineClass(name, loader, mCookie, this, suppressed); }

    loadClassBinaryName 方法中调用了 defineClass 方法:

    libcore/dalvik/src/main/java/dalvik/system/DexFile.java

    private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> supp ressed) { 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; }

    在注释1处调用了 defineClassNative方法来加载dex相关文件,这个方法是Native方 法,这里就不再进行分析,有兴趣的读者可以自行阅读源码。ClassLoader的加载过程就是 遵循着双亲委托模式,如果委托流程没有检查到此前加载过传入的类,就调用ClassLoader findClass方法,Java层最终会调用DexFiledefineClassNative方法来执行查找流程, 如图12.4所示。

     

                                                    图12-4 ClassLoader查找流程

    BootClassLoader 的创建

    BootClassLoader是在何时被创建的呢?这得先从Zygote进程开始说起,Zygotelnitmain方法如下所示:

    frameworks/base/core/iava/com/android/internal/os/Zygotelnit.java

     

    public static void main(String argv[]) { try { preload(bootTimingsTraceLog); } }

    main方法是Zygotelnit的入口方法,其中调用了 Zygotelnitpreload方法,在preload 方法中又调用了 ZygotelnitpreloadClasses方法,如下所示:

    frameworks/base/core/java/com/android/intemal/os/Zygotelnit.java

    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes”; private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); InputStream is; try { //将/system/etc/preloaded-classes 文件封装成 FilelnputStream is = new FilelnputStream(PRELOADED_CLASSES);//l } catch (FileNotFoundException e) { Log.e (TAG, "Could't find " + PRELOADED_CLASSES + "."); return; } try { //将 FilelnputStream 封装为 BufferedReader BufferedReader br= new BufferedReader(new InputStreamReader(is), 256);//2 int count = 0; String line; while ((line = br.readLine()) != null) {//3 line = line.trim(); if (line. startsWith("#") || line .equals("") ) { continue; } Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line); try { if (false) { Log.v(TAG, "Preloading " + line +"..."); } Class.forName(line, true, null);//4 count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); } } catch (IOException e) { Log.e(TAG, "Error reading ” + PRELOADED_CLASSES + ".", e); } finally { } }

    preloadClasses方法用于Zygote进程初始化时预加载常用类。在注释1PRELOADED CLASSES 的值为将/system/etc/preloaded-classes 文件封装成 FilelnputStream, preloaded-classes文件中存有预加载类的目录,这个文件在系统源码中的路径为 frameworks/base/preloaded-classes,这里列举一些 preloaded-classes 文件中的预加载类名称, 如下所示:

    android.app.ApplicationLoaders android.app.ApplicationPackageManager android.app.ApplicationPackageManager$OnPennissionsChangeListenerDelegate android.app.ApplicationPackageManager$ResourceName android.app.ContentProviderHolder android.app.ContentProviderHolder$1 android.app.Contextlmpl android.app.ContextImpl$ApplicationContentResolver android.app.DexLoadReporter android.app.Dialog android.app.Dialog$ListenersHandler android.app.DownloadManager android.app.Fragment

    可以看到preloaded-classes文件中的预加载类的名称有很多都是我们非常熟知的。预加 载属于拿空间换时间的策略,Zygote环境配置得越健全越通用,应用程序进程需要单独做 的事情也就越少,预加载除了预加载类,还有预加载资源和预加载共享库,因为不是本节 的重点,这里就不再延伸讲下去了。回到preloadClasses方法的注释2处,将FilelnputStream 封装为BufferedReader,并在注释3处遍历BufferedReader,读出所有预加载类的名称,每 读出一个预加载类的名称就调用注释4处的代码加载该类,ClassforName方法如下所示:

    libcore/ojluni/src/main/java/java/lang/Class.java

    @CallerSensitive public static Class<?> forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException{ if (loader == null) { loader = BootClassLoader.getlnstance();//l } Class<?> result; try { result = classForName(name, initialize, loader);//2 } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; }

    在注释1处创建了 BootClassLoader,并将BootClassLoader实例传入到了注释2处的 classForName方法中,classForName方法是Native方法,它的实现由C/C++代码来完成, 如下所示:

    @FastNative static native Class<?> classForName(String className, boolean shouldlnitialize, ClassLoader ClassLoader) throws ClassNotFoundException;

    Native方法这里就不再分析了,我们知道了 BootClassLoader是在Zygote进程的Zygote 入口方法中被创建的,用于加载preloaded-classes文件中存有的预加载类。

    PathClassLoader 的创建

    PathClassLoader的创建也得从Zygote进程开始说起,Zygote进程启动SystemServer 进程时会调用ZygotelnitstartSystemServer方法,如下所示:

    frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

     

    private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /*1*/ pid = Zygote.forkSystemServer(parsedArgs.uid,parsedArgs.gid,parsedArgs.gids,parsedArgs.debugFlags,null,parsedArgs.permittedCapabilities,parsedArgs.effectivecapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } if (pid == 0) {//2 if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } handleSystemServerProcess(parsedArgs);//3 } return true; }

    在注释1,Zygote进程通过forkSystemServer方法fork自身创建子进程(SystemServer 进程)。在注释2处如果forkSystemServer方法返回的pid等于0,说明当前代码是在新创 建的SystemServer进程中执行的,接着就会执行注释3处的handleSystemServerProcess方法:

    frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

    private static void handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) throws Zygote.MethodAndArgsCaller { if (parsedArgs.invokeWith != null) { } else { ClassLoader cl = null; if (systemServerClasspath != null) { cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//l Thread.currentThread().setContextClassLoader(cl); } Zygotelnit.zygotelnit(parsedArgs.targetSdkVersion, parsedArgs. remainingArgs, cl); } }

    在注释1处调用了 createPathClassLoader方法,如下所示:

    frameworks/base/core/java/com/android/intemal/os/Zygotelnit.java

     

    static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion{ String libraryPath = System. getProperty ("java.library.path"); return PathClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,ClassLoader.getSystemClassLoader(),targetSdkVersion, true /* isNamespaceShared */); }

    createPathClassLoader 方法中又调用了 PathClassLoaderFactory createClassLoader 方法,看来PathClassLoader是用工厂来进行创建的:

    frameworks/base/core/java/com/android/internal/os/PathClassLoaderFactoryjava

    public static PathClassLoader createClassLoader(String dexPath,String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared) { PathClassLoader pathclassloader = new PathClassLoader(dexPath, librarySearchPath, parent); return pathclassloader; }

    PathClassLoaderFactory createClassLoader 方法中会创建 PathClassLoader.讲到这里可以得出结论,PathClassLoader是在SystemServer进程中采用工厂模式创建的。

    本章小结

    本章分别对JavaAndroidClassLoader进行解析,通过JavaAndroidClassLoader 的类型和ClassLoader的继承关系,就可以很清楚地看出它们的差异,主要有以下几点:

    Java的引导类加载器是由C++编写的,Android中的引导类加载器则是用Java编写的。Android的继承关系要比Java继承关系复杂一些,提供的功能也多。由于Android中加载的不再是Class文件,因此Android中没有ExtClassLoaderAppClassLoader,替代它们的是 PathClassLoader DexClassLoader
    Processed: 0.010, SQL: 9