解析ClassLoader

解析ClassLoader

       我们知道Dalvik(DVM)和ART加载的是dex文件,而JVM加载的是Class文件,因此Android和Java的类加载器ClassLoader是有去别的,因此本文分别介绍Java中的ClassLoader和Android中的ClassLoader。本文是基于JDK8 和 Android 8.1.0系统来分析 ClassLoader。

    1. Java中的ClassLoader

     1.1 ClassLoader的类型

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

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

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

       1)$JAVA_HOME/jre/bin 目录。

       2)-Xbootclasspath 参数指定的目录。

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

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

       打印结果如下:

D:\Program Files\Java\jre1.8.0_131\lib\resources.jar;
D:\Program Files\Java\jre1.8.0_131\lib\rt.jar;
D:\Program Files\Java\jre1.8.0_131\lib\sunrsasign.jar;
D:\Program Files\Java\jre1.8.0_131\lib\jsse.jar;
D:\Program Files\Java\jre1.8.0_131\lib\jce.jar;
D:\Program Files\Java\jre1.8.0_131\lib\charsets.jar;
D:\Program Files\Java\jre1.8.0_131\lib\jfr.jar;
D:\Program Files\Java\jre1.8.0_131\classes

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

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

       1)加载 $JAVA_HOME/jre/lib/ext 目录。

       2)系统属性 java.ext.dir 所指定的目录。

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

public class ClassLoaderTest {
	public static void main(String[] args) {
		String class_loader_path = System.getProperty("java.ext.dirs");
		System.out.println(class_loader_path);
	}
}

       打印结果如下:

D:\Program Files\Java\jre1.8.0_131\lib\ext;
C:\windows\Sun\Java\lib\ext

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

       Java 中的实 现类为 AppClassLoader ,因此可以简称为 AppCiassLoader ,同时它又可以称作 System ClassLoader (系统类加载器),这是因为 AppClassLoader 可以通过 ClassLoader 的  getSystemClassLoader 方法 获取到。 它用来加载以 下目录中的类库:
       
       1)当前应用程序的ClassPath 目录。
 
       2)系统属性 java.class.path 指定的目录。
 
       4. Custom ClassLoader(自定义类加载器)
   

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

 

     1.2 ClassLoader 的继承关系

       当我们运行一个Java程序的时候会用到几种类型的类加载器呢?首先我们来看如下代码:

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

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

loader = sun.misc.Launcher$AppClassLoader@2a139a55
loader = sun.misc.Launcher$ExtClassLoader@7852e922
       第1行说明加载 ClassLoaderTest 的类加载 器是 A ppClassLoader , 第2 行说明AppClassLoader的父加载器为 ExtClassLoader 。 至于为何没有打印出 Ex tClassLoader 的父加 载器 Bootstrap ClassLoader ,这是因为 Bootstrap ClassLoader 是由C/C++ 编写 的, 并不是一个  Java 类,因此我们无法在 Java 代码中 获取 它的引用 。系统 所提供的类 加载器有3种 类型, 但是系统提供的 ClassLoader 却不只有3 个。另外, AppClassLoader 的父类加载 器为 ExtClassLoader ,并不代表 AppClassLoader继承自 ExtClassLoader, ClassLoader 的继承关系 如图 所示。
解析ClassLoader_第1张图片 图1  ClassLoader 的继承关系

 

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

       1)ClassLoader 是一个抽象类,其中定义了ClassLoader的主要功能。

       2)SecureClassLoader 继承了抽象类ClassLoader,但是SecureClassLoader 并不是 ClassLoader的实现类,而是拓展了ClassLoader 类,加入了权限方面的功能,加强了ClassLoader的安全性。

       3)URLClassLoader 继承自 SecureClassLoader ,可以通过URL路径从jar文件和文件夹中加载类和资源。

       4)ExtClassLoader 和 AppClassLoader 都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher 是 Java 虚拟机的入口应用,ExtClassLoader 和 AppClassLoader 都是在Launcher中进行初始化的。 

 

     1.3 双亲委托模式

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

解析ClassLoader_第2张图片 图2  双亲委托模式

 

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

       1)自定义类加载器先从缓存中查找 Class 文件是否已经加载,如果已经加载就返回该 Class ,如果没加载则委托给父加载器也就是 AppClassLoader

       2)按照图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 所指的目录中进行查找,如果找到就加载并返回该Class, 找不到则交给 AppClassLoader。

       5)AppClassLoader 则在 Classpath 目录中或者系统属性 java.class.path 指定的目录中进行查找,如果找到就加载并返回改Class,找不到就交给我们自定义的类加载器 ,如果还找不到则抛出异常。

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

 

       为了更好的理解类加载的步骤,我们来分析一下JDK8的ClassLoader源码,首先我们先看ClassLoader的loadClass方法,代码如下所示:

 protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 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) {
                        c = parent.loadClass(name, false); // ... 2
                    } else {
                        c = findBootstrapClassOrNull(name); // ... 3
                    }
                } 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.
                    long t1 = System.nanoTime();
                    c = findClass(name); // ... 4

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
       在注释1 处用来检查传入的类是否已经加载,如果已经加载则 后面的代码不会执行, 最后会返回该加载类。没有加载会接着向下执行,在注释2处,如果父类加载器不为 null, 就调用父类加载器的 load Class 方法。   如果父类加载器为 null 就调用注释3 处的 findBootstrapClassOrNull 方法,这个方法内部调用了 Native 方法 findBootstrapClass , findBootstrapClass 方法中最终会用  Bootstrap Classloader 来检查该类是否已经加载,如果没 有加载就说明向上委托流程中没有加载该类,则 调用 注释4处的 findClass 方法继续向下进行查 找流程。

 

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

       1) 避免重复加载,如果加载过一次 Class ,就不需要再次加载,而是直接读取已经加载的 Class。

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

 

    1.4 自定义ClassLoader

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

       1)自定义一个类加载器并继承抽象类ClassLoader。

       2)复写findClass方法,并在findClass方法中调用defineClass方法。

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

public class Test{
	public void test(){
		System.out.println("I'm the test class on disk D");
	}
}

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

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
	private String path;

	public MyClassLoader(String path) {
		this.path = path;
	};
	
	@Override
	protected Class findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		Class clazz = null;
		byte[] classData = loadClassData(name); // ... 1
		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 FileInputStream(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(IOException 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.lastIndexOf(".");
		if(index == -1) {
			return name + ".class";
		}else {
			return name.substring(index + 1) + ".class";
		}
	}
	
}

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {
	public static void main(String[] args) {
		MyClassLoader loader = new MyClassLoader("D:\\lib"); // ... 1
		try {
			Class clazz = loader.loadClass("Test"); // ... 2
			if (clazz != null) {
				Object test = clazz.newInstance();
				System.out.println("test 的类加载器为: " + test.getClass().getClassLoader());
				Method method = clazz.getDeclaredMethod("test", null);
				method.invoke(test,null); // ... 3
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
       在注释1 处创建 My ClassLoader 并传入要加载类的路径,在注释2 处加载 Class 文件, 需要注意的是,不要在项目工程中存在Test.j ava 的 文件,否则就不会使 MyClassLoader 来加载,而是使用 AppClassLoader 来负责 加载,这样我们定义的 My ClassLoader 就变 得毫 无意义。接下来 在注释3 处通过反射来调用Test 的 test 方法 ,打 印结果如下:
test 的类加载器为: MyClassLoader@4e25154f
I'm the test class on disk D

       使用MyClassLoader来加载Class文件,从上面的输出可以看出,Test.class文件成功加载到JVM,并且Test 的 test 方法也正确执行。

    2. Android 中的 ClassLoader 

     2.1 ClassLoader的类型

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

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

       1. BootClassLoader

       Android 系统启动时会使用 BootClassLoader 来预加载常用类,与 Java 中的 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 getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    ...
}
       BootCla ssLoader 是 ClassLoader 的内部类,并继承自 Cl assLoader。  BootC las sL oader 是一 个单例类,需要注意的是 BootClassLoader 的访问修饰符是 默认 的,只 有在一 个包中才可以访问,因此我们在应用程序中是无法直接调用的。
 
 
      2. DexClassLoader
 
      DexClassLoader 可以加载 dex 文件以及包含dex的压缩文件(apk 和 jar 文件),不管加载哪种文件, 最终都要加载 dex 文件,为了便于理解和叙述,将 dex 文件以及包  dex 的压缩文件统称为 dex 相关文件。  De xC la ssLoad er 的代码如下所示:
 

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

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

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

       1)dexPath: dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"。

       2)optimizedDirectory:解压的 dex 文件存储路径,这个路径必须是一个内部存储路径,在一般情况下,使用当前应用程序的私有路径:/data/data//... 。

       3)librarySearchPath:包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以为null。

       3)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 中实现。
 
       PathClassLoad er 的构 造方法中没有参数 opt imizedDirectory, 这是 因为 Pa thClassLoader 已经默认了参数 optimizedDirectory 的值为 / data / dal vik-cache ,很显然 PathClassLoader 无法 定义解压的 dex 文件存储路径,因此 PathClassLoader   通常用来加载已经安装的 a pk 的  dex 文件(安装的 apk 的 dex 文件 会存储在 data / dal vik-cache 中)。

 

    2.2 ClassLoader 的继承关系

       在Android中运行一个应用程序一般需要用到几种类型的类加载器呢?收下我们来看如下代码:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d(TAG, classLoader.toString());
            classLoader = classLoader.getParent();
        }
    }
}

       首先我们得到 MainActivity 的类加载器,然后通过Log打印出来,接着通过循环打印出当前类的类加载器的父类加载器,直到没有父类加载器实终止循环,打印结果如下所示:

07-27 07:03:57.199 5967-5967/com.lx.class_loader D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.lx.class_loader-1/base.apk"],nativeLibraryDirectories=[/data/app/com.lx.class_loader-1/lib/x86, /vendor/lib, /system/lib]]]
07-27 07:03:57.199 5967-5967/com.lx.class_loader D/MainActivity: java.lang.BootClassLoader@88d5d94
       可以看到有两种类加载器, 一 种是 PathClassLoader ,另一 种则是 BootClassLoader。  DexPathList 中包 含了很多 apk 的路径,其中 /data/app/com.lx.class_loader-1/base.apk  就是示例应 用安装在手机上的 位置。 DexPathList 是在 BaseDexClassLoader 的构造方法中创建的 ,里面存储了 d ex 相关文件的路径,在 ClassLoader 执行双亲委托模式的查找流程时会从 DexPathList 中进行查 找。
 
       除了 上面所讲的3 种主要的类加载器外,An droid 还提供了其他的类加载器和  Class Loader 相关类, ClassLoader 的继承关系如图 3 所示。
解析ClassLoader_第3张图片 图3  Android8.1.0 中 ClassLoader 的继承关系
   
       可以看到图 3 共有8个  ClassLoader 相关类,其中有一些和 Java 中的 ClassLoader 相关类十分类似,下面简单对它们进行介绍:

       1)ClassLoader 是一个抽象类,其中定义了 ClassLoader 的主要功能。 BootClassLoader 是它的内部类。

       2)SecureClassLoader 类和 JDK8 中的 SecureClassLoader 类的代码是一样的,它继承了抽象类 ClassLoader。SecureClassLoader 并不是 ClassLoader 的实现类,而是拓展了 ClassLoader 类,加入了权限方面的功能,加强了 ClassLoader 的安全性

       3)URLClassLoader 类和 JDK8 中的 URLClassLoader 类的代码是一样的,它继承自 SecureClassLoader ,用来通过 URL 路径从 jar 文件和文件夹中加载类和资掘。

       4)InMemoryDexClassLoader 是 Android 8.0 新增的类加载器,继承自 BaseDexClassLoader ,用于加载内存中的 dex 文件。

       5)BaseDexClassLoader 继承自 ClassLoader ,是抽象类 ClassLoader 的具体实现类, PathClassLoader 、DexClassLoader、 InMemoryDexClassLoader 都继承自它。

    2.3 ClassLoader 的加载过程

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

libcore/ojluni/src/main/java/java/lang/Classloader.java
    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) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false); // ... 2
                    } 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.
                    c = findClass(name); // ... 5
                }
            }
            return c;
    }
       ClassLoader 的  loadClass 方法 1.3 节讲的 load Class 方法 JDK 中  ClassLoader 的  load Class 方法 )类似。  在注释1处 用来检查 传入的类是否已经加载,如果已经加载就返回该类,如果没有加载就在注释2处判断父加载器是否存在,存在就调 用父加载器的 load Class 方法,如果不存在就调用注释3处的 findBootstrapClassOrNull 方法,这个方法会直接返回 null 。如果注释4 处的代码成 立,说明向上委托流程没有检查出类已经被 加载,就 会执行注释5 处的 find Class 方法 来进行查找流程, find Class 方法 如下所示:
 
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 class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); // ... 1

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }
    ...
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
        Class c = pathList.findClass(name, suppressedExceptions); // ... 2
        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;
    }
    ...
}

       在 BaseDexClassLoader 的构造方法的注释1处创建了 DexPathList,在注释2处调用了 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处遍历 Ele ment 数组 d exElement,在注释2处 调用 E leme nt 的  find Class  方法, Element 是 D expa thList 的静态内部类,代码如下所示:
 

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

    /*package*/ 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(name, definingContext, 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 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;
    }
       在注释1处调 用了 defineClassN ative 方法来 加载 dex 相关文件,这个方法是 Native 方法 ,这里就不再进行分析, ClassLoader 的加载过程就是遵循着双亲委托模式,  如果委托流程没有检查 到此前加载过传入的类,就调用 ClassLoader 的  findClass 方法 Java 层最终会调用 DexFile 的  defineClassN ative 方法 来执行查找流程。
 
 

     2.4 BootClassLoader 的创建

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

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

    public static void main(String argv[]) {
        ...
        try {
            ...
            if (!enableLazyPreload) {
                ...
                preload(bootTimingsTraceLog);
                ...
            } else {
                Zygote.resetNicePriority();
            }
           ...
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            zygoteServer.closeServerSocket();
        }
        ...
    }

       在ZygoteInit 的 main 方法中调用了 preload 方法,而 平reload 方法又调用了ZygoteInit 的 preloadClasses 方法,代码如下所示:

frameworks/base/core/java/com/android/internal/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文件的FileInputStream
            is = new FileInputStream(PRELOADED_CLASSES); // ... 1
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
        ...
        try {
            // 将FileInputSteam
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256); // ... 2

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) { // ... 3
                // Skip comments and blank lines.
                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 (UnsatisfiedLinkError e) {
                    Log.w(TAG, "Problem preloading " + line + ": " + e);
                } catch (Throwable t) {
                    Log.e(TAG, "Error preloading " + line + ".", t);
                    if (t instanceof Error) {
                        throw (Error) t;
                    }
                    if (t instanceof RuntimeException) {
                        throw (RuntimeException) t;
                    }
                    throw new RuntimeException(t);
                }
                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
            }

            Log.i(TAG, "...preloaded " + count + " classes in "
                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            ...
        }
    }

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

android.app.ApplicationLoaders 
android.app.ApplicationPackageManager 
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate 
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder 
android.app.ContentProviderHolder$1 
android.app.ContextImpl 
android.app.Contextimpl$ApplicationContentResolver 
android.app.DexLoadReporter 
android.app.Dialog 
android.app.Dialog$ListenersHandler
android.app.DownloadManager 
android.app.Fragment
       可以看到  preloaded_c lasses 文 件中的预加载类 的名称有很多 我们非 常熟 知的。 预加载属于 拿空 间换时间的策 略, Zygote 环境配 置得 越健全越通 用, 应用程序进程需要单 独做的事情也就越 少,预 加载除了加 载类 ,还 有预加载资源 和预加 载共享库,因为 不是本节重 点,这里就不再延伸讲下了。回到 preloadC lasses 方法 的注释2 处,将 File lnputStream 封装为 BufferedReader , 并在注释3 处遍历  BufferedReader ,读 出所有 预加载类的名 称,每读出一个预加载类 的名称就 调用 注释4 处的代码加载该类, Class 的  for Name 方法如下所示:
 
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.getInstance(); // ... 1
        }
        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 处创 建了 BootC lassLoader ,并将 BootClassLoader 实 例传入到了注释2 处的 classForName 方法中, classForName 方法 Native 方法,它的实现由 C/C++  代码来完成, 如下所示:
    @FastNative
    static native Class classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;
       Native 方法这里就不再分析了,我们知道了 BootClassLoader 是在 Zygote 进程的 Zygote 入口方法中被创建的,用于加载 preloaded- classes 文件中存有的预加载类。

 

    2.5 PathClassloader 的创建

       PathClassLoader 的创建也得从 Zygote 进程开始说起, Zygote 进程启动 SystemServer 进程时会调用 Zygotelnit的 handleSystemServerProcess 方法,如下所示:
 
frameworks/base/core/java/com/android/internal/os/Zygotelnit.java
 
    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
        ...
        if (parsedArgs.invokeWith != null) {
            ...
        } else {
            ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); // ... 1

                Thread.currentThread().setContextClassLoader(cl);
            }

            /*
             * Pass the remaining arguments to SystemServer.
             */
            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
        }

        /* should never reach here */
    }

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

frameworks/base/core/java/com/android/internal/os/Zygotelnit.java
    static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
        String libraryPath = System.getProperty("java.library.path");

        return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
                ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
                null /* classLoaderName */);
    }

       在 createPathClassLoader 方法中有调用了 ClassLoaderFactory 的 createClassLoader 方法,代码如下所示:

frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java
 
    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }
       在 ClassLoaderFactory 的  createClassLoader 方法 中会创建 PathClassLoader 。讲到这里可以得出结论, PathClassLoader 是在 SystemServer 进程中采 用工厂模式创建的。
 

你可能感兴趣的:(解析ClassLoader,Android,Java)