理解 ClassLoader

热修复和插件化是目前比较热门的技术,要想更好地掌握它们需要先了解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所示。

理解 ClassLoader_第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

理解 ClassLoader_第2张图片

                                                                                               图12-2双亲委托模式

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

  1. 自定义类加载器首先从缓存中查找Class文件是否已经加载,如果已经加载就返回该Class,如果没加载则委托给父加载器也就是AppClassLoader
  2. 按照图12-2中虚线的方向递归步骤1。
  3. 一直委托到 Bootstrap ClassLoader,如果 Bootstrap ClassLoader 查找缓存也没有加载Class文件,则在$JAVA_HOME/jre/lib目录中或者-Xbootclasspath参数指定的目录中进行查找,如果找到就加载并返回该Class,如果没有找到则交给子加载器ExtClassLoader
  4. ExtClassLoader在$JAVA_HOME/jre/lib/ext目录中或者系统属性java.ext.dir所指定的目录中进行查找,如果找到就加载并返回,找不到则交给AppClassLoader
  5. AppClassLoadeClasspath目录中或者系统属性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需要如下两个步骤:

  1. 定义一个自定义ClassLoade并继承抽象类ClassLoader
  2. 复写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//...
  • librarySearchPath包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以 为 null
  • parent父加载器。

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打印出来,接着通 过循环打印出当前类的类加载器的父加载器,直到没有父加载器终止循环,打印结果如下所示:

理解 ClassLoader_第3张图片

可以看到有两种类加载器,一种是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所示。

理解 ClassLoader_第4张图片

                                                                图 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 suppressedExceptions = new ArrayList();
    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 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 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 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 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所示。

理解 ClassLoader_第5张图片 

                                                图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

你可能感兴趣的:(理解 ClassLoader)