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需要两个步骤:

  1. 定义一个自定义ClassLoade并继承抽象类ClassLoader。
  2. 复写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;
    }