Java中的ClassLoader
通过ClassLoader来查找和加载Class文件到java虚拟机中,系统ClassLoader主要包括这三种,分别是Bootstrap ClassLoader、 Extensions ClassLoader和 App ClassLoader。ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher 是Java虚拟机的入口应用,ExtClassLoader和AppClassLoader都是在Launcher中进行初始化的。ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能,SecureClassLoader继承了抽象类ClassLoader加入了权限方面的功能,加强了ClassLoader的安全性;
URLClassLoader继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
BootstrapClassLoader
用C/C++代码实现的加载器,加载Java虚拟机运行时所需要的系统类,如java.lang.*、java.uti.*
等这些系统类,它们默认在$JAVA_HOME/jre/lib目录中,也可以通过启动Java虚拟机时指定-Xbootclasspath选项,来改变Bootstrap ClassLoader的加载目录。
ExtClassLoader
加载 Java 的拓展类 ,拓展类的jar包一般会放在$JAVA_HOME/jre/lib/ext目录下,用来提供除了系统类之外的额外功能。也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径。
AppClassLoader
加载当前应用程序Classpath目录下的所有jar和Class文件。也可以加载通过-Djava.class.path选项所指定的目录下的jar和Class文件。
双亲委派
看看ClassLoader类的loadClass采用双亲委托模式
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(name)) {
//检查是否已经加载,加载过了直接返回,没有的话继续向下执行
Class c = this.findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (this.parent != null) {
//用父类加载器的loadClass方法
c = this.parent.loadClass(name, false);
} else {
//会用Bootstrap Classloader来查找类
c = this.findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
c = this.findClass(name);
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
this.resolveClass(c);
}
return c;
}
}
loadClass方法自下而上委托给父类,直到最顶层Bootstrap,然后下上而下查找, 都找不到自己在findClass。
采用双亲委派模式的好处有两点:
1、已经加载过的类不需要二次重复加载,直接从缓存读取
2、更加安全,避免用户用自己定义的类替代系统类
CustomClassLoader
系统提供的类加载器只能够加载指定目录下的jar包和Class文件,需要加载网络上的或者是本地磁盘某一文件中的jar包和Class文件则需要自定义ClassLoader。
实现自定义ClassLoader需要两个步骤:
- 定义一个自定义ClassLoade并继承抽象类ClassLoader。
- 复写findClass方法,并在findClass方法中调用defineClass方法(在findClass中通过io流拿到class文件的字节数组,然后调用ClassLoader的defineClass就可以转为Class对象)
之后就可以通过对Class的反射然后为所欲为了。。
Android的ClassLoader
java中的ClassLoader说完了,下面一起来看看Android的ClassLoader,Java中的ClassLoader可以加载jar文件和Class文件,而在安卓中,加载的是dex相关文件。安卓中系统ClassLoader也分为三种哦,分别是BootClassLoader、PathClassLoader和DexClassLoader。
BootClassLoader
继承ClassLoader,也被定义在了ClassLoader里,BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。
BootClassLoader是在Zygote进程的入口方法main中创建的
PathClassLoader
加载系统类和应用程序的类,PathClassLoader继承自BaseDexClassLoader,构造方法实现都在BaseDexClassLoader中。
PathClassLoader则是在Zygote进程创建SystemServer进程时创建的
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);
}
}
DexClassLoader
可以用来加载外部的一些dex、apk、class文件,DexClassLoader 也继承自BaseDexClassLoader ,方法实现也都在BaseDexClassLoader中。构造方法的参数要比PathClassLoader多一个optimizedDirectory参数。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
参数optimizedDirectory就是代表存储ODEX(优化过的DEX)文件的路径,PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache
BaseDexClassLoader
既然上面两个类都继承该类,那么来看看
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
}
解释下构造方法四个参数:
- dexPath:包含classes和resources的APK或jar文件的路径,多个路径之间用File.pathSeparator隔开。
- optimizedDirectory:将dex解压优化成odex的路径,下次直接从该目录加载,所以必须为内部路径。
- librarySearchPath:使用的C/C++库存放的路径,多个路径之间用File.pathSeparator隔开。
- parent:父ClassLoader
BaseDexClassLoader的findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
在上面BaseDexClassLoader构造方法中构建了一个DexPathList,并且findClass中是通过调用pathList.findClass。
DexPathList
final class DexPathList {
private Element[] dexElements;
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
}
}
在构造方法中构建dexElements
,然后进入makeDexElements
方法中循环files通过DexFile的静态方法loadDexFile(file, optimizedDirectory, loader, elements)
加载DexFile赋值给dexElements
数组
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements)throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
最后会发现,BaseDexClassLoader构造函数初始化的时候,会走到DexFile的loadDex中去加载dex文件。BaseDexClassLoader.findClass方法,则会走到DexFile的loadClass方法中去加载class,最后调到了defineClass。这里注意,java中的ClassLoader里直接有defineClass方法,而安卓的defineClass是在DexFile这个类中
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader, null);
}
/**
* See {@link #loadClass(String, ClassLoader)}.
*
* This takes a "binary" class name to better match ClassLoader semantics.
*
* @hide
*/
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
综上所述,安卓的ClassLoader中加载Dex和加载Class最终都是掉到了DexFile
这个类,他很重要。
ClassLoader
Android中默认无父构造器传入的情况下,默认父构造器为一个PathClassLoader且此PathClassLoader父构造器为BootClassLoader。
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private final ClassLoader parent;
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
看看loadClass方法,和java的ClassLoader一样,双亲委派模式
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> 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);
}
}
return c;
}