java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,而后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此以外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等,不管是 JVM 仍是 Dalvik 都是经过 ClassLoader 去加载所须要的类。html
对于任意一个类,都须要由他的类加载器和这个类自己一同肯定其在Java虚拟机的惟一性。比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,不然,即便两个雷来源于同一个class文件,被同一个虚拟机加载,只要加载的ClassLoader不一样,两个类就一定不相等。java
在ClassLoader类中提供了几个重要的方法:android
方法说明git
表示类名称的 name参数的值是类的二进制名称。须要注意的是内部类的表示,如 com.example.Sample$1
和 com.example.Sample$Inner
等表示方式。算法
在Java中的类加载器主要有两种类型的,一种是系统提供的,另一种则是由Java应用开发人员编写的,系统主要提供的类加载器有三个:bootstrap
bootstrap class loader是全部的类加载器的父类,而后是extensions class loader,而后是system class loader,而后才是开发人员自主编写的ClassLoader,关系以下:api
每个类都有本身的ClassLoader,咱们能够经过以下代码来查看全部的类加载器的引用:数组
public static void main(String[] args) { ClassLoader loader = Test.class.getClassLoader(); while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } 结果: sun.misc.Launcher$AppClassLoader@4554617c -->SystemClassLoader sun.misc.Launcher$ExtClassLoader@677327b6 -->ExtensionClassLoader
在结果中能够看到两个Loader的存在,AppClassLoader即system class loader,能够经过 ClassLoader.getSystemClassLoader()来进行测试获取是否为系统类加载器。ExtClassLoader则是引导类classloader。从上面的结果能够看出,并无获取到ExtClassLoader的父Loader,缘由是Bootstrap Loader(启动类加载器)是用C语言实现的,ExtClassLoader找不到一个肯定的返回父Loader的方式,因而就返回null。缓存
若是咱们调用ClassLoader来加载一个类,都会调用到loadClass(String)来进行加载,这里就从这里j简单跟踪一下ClassLoader的源码:安全
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); if (c == null) { long t0 = System.nanoTime(); 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. long t1 = System.nanoTime(); c = findClass(name); // 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; } }
从上面的注释均可以看明白loadClass()的做用:
从loadClass方法中能够很明确的看出来ClassLoader的设计是__双亲委托模型__。,每一个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),Bootstrap ClassLoader自己没有父类加载器,但能够用做其它ClassLoader实例的的父类加载器。当一个ClassLoader实例须要加载某个类时,它会试图亲自搜索某个类以前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,若是没加载到,则把任务转交给Extension ClassLoader试图加载,若是也没加载到,则转交给App ClassLoader 进行加载,若是它也没有加载获得的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。若是它们都没有加载到这个类时,则抛出ClassNotFoundException异常。不然将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
为啥使用双亲委托模型
由于这样能够避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,咱们试想一下,若是不使用这种委托模式,那咱们就能够随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在很是大的安全隐患,而双亲委托的方式,就能够避免这种状况,由于String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,因此用户自定义的ClassLoader永远也没法加载一个本身写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
关于Class.forName()和使用ClassLoader.loadClass()的区别
Class.forName(className)方法,其实调用的方法是Class.forName(className,true,classloader);注意看第2个boolean参数,它表示的意思,在loadClass后必须初始化。能够查阅这篇文章,这里类加执行初始化这一步骤会进行后,目标对象的static块代码执行,static参数也被初始化。
ClassLoader.loadClass(className)方法,其实他调用的方法是ClassLoader.loadClass(className,false);仍是注意看第2个 boolean参数,该参数表示目标对象被装载后不进行链接,就天然不会去执行初始化的操做,这就意味这不会去执行该类静态块中间的内容。所以2者的区别就显而易见了。
这里只是作个简单的Demo来测试,首先写一个CLTest类:
package test; public class CLTest{ public void sys(){ System.out.println("hello classloader"); } }
使用javac命令生成一个对应的.class文件,并放在项目根目录下。而后编写本身的ClassLoader类:
package test; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class Test extends ClassLoader{ @Override protected Class<?> findClass(String name) { Class clazz=null; try { byte[] by=toByteArray("CLTest.class"); clazz=defineClass(name,by,0,by.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.toString()); } return clazz; } public static void main(String[] args) { Test test=new Test(); try { Class clazz= test.loadClass("test.CLTest"); if (clazz!=null) { clazz.getMethod("sys").invoke(clazz.newInstance(), null); } } catch (Exception e) { System.out.println(e.toString()); } } public static byte[] toByteArray(String filename) throws IOException{ File f = new File(filename); if(!f.exists()){ throw new FileNotFoundException(filename); } ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length()); BufferedInputStream in = null; try{ in = new BufferedInputStream(new FileInputStream(f)); int buf_size = 1024; byte[] buffer = new byte[buf_size]; int len = 0; while(-1 != (len = in.read(buffer,0,buf_size))){ bos.write(buffer,0,len); } return bos.toByteArray(); }catch (IOException e) { e.printStackTrace(); throw e; }finally{ try{ in.close(); }catch (IOException e) { e.printStackTrace(); } bos.close(); } } } //输出结果:hello classloader
再看下android中的classloader:
ClassLoader loader = DebugActivity.class.getClassLoader(); Log.e("dsdads","system class loader="+ ClassLoader.getSystemClassLoader().getClass().getSimpleName()+"\n"); while (loader != null) { Log.e("dsdads", "loader="+loader.getClass().getSimpleName()+"\n"); loader = loader.getParent(); } //输出结果 system class loader=PathClassLoader loader=PathClassLoader loader=BootClassLoader
因为android自己虚拟机不一样,android自身实现了一套与java基本相同的加载机制,BootClassLoader是系统启动时建立的,至关于引导类加载器,PathClassLoader则是系统类加载器。这两个类的关系能够在Android源码中直接得以体现:
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()); } ... public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); } return instance; }
BootClassLoader是PathClassLoader的parent。
查看ClassLoader对应的实现类有:
这里面URLClassLoader又是SecureClassLoader的子类,用于加载jar包文件,在android上是不能使用的。DexClassLoader和PathClassLoader是BaseDexClassLoader子类,用于加载Dex文件。
先看下ClassLoader的loadClass()方法吧:
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; }
这里能够看到Android中的ClassLoader基本遵循了Java的模式,使用双亲委托模式进行处理文件,只不过区别在于resolve变量在Android中并无处理。咱们仍是直接看BaseDexClassLoader吧:
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; /** * Constructs an instance. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be written; may be {@code null} * @param libraryPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } @Override 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继承自ClassLoader,主要实现了findClass()方法,首先查看构造方法:
使用上述的参数去实例化DexPathList类。这里在findClass()方法中能够看到,主要调用DexPathList方法去得到Class对象,所以咱们转向先研究一下DexPathList类。首先查看一下构造方法:
private final Element[] dexElements; /** List of native library path elements. */ private final Element[] nativeLibraryPathElements; /** List of application native library directories. */ private final List<File> nativeLibraryDirectories; /** List of system native library directories. */ private final List<File> systemNativeLibraryDirectories; ... public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // save dexPath for BaseDexClassLoader this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);//构造dex的数组 // Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. This class loader's library path for application libraries (libraryPath): // 1.1. Native library directories // 1.2. Path to libraries in apk-files // 2. The VM's library path from the system property for system libraries // also known as java.library.path // // This order was reversed prior to Gingerbread; see http://b/2933456. this.nativeLibraryDirectories = splitPaths(libraryPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);//构造native的List this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);//构造native的Elements数组 ... }
这里一步一步看,能够知道首先生成dexElments,先从splitPaths()方法入手:
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { List<File> result = new ArrayList<>(); if (searchPath != null) { for (String path : searchPath.split(File.pathSeparator)) {//File.pathSeparator在Linux系统上为: if (directoriesOnly) { try { StructStat sb = Libcore.os.stat(path); if (!S_ISDIR(sb.st_mode)) { continue; } } catch (ErrnoException ignored) { continue; } } result.add(new File(path)); } } return result; } private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; } private static void splitAndAdd(String searchPath, boolean directoriesOnly, ArrayList<File> resultList) { if (searchPath == null) { return; } for (String path : searchPath.split(":")) { try { StructStat sb = Libcore.os.stat(path); if (!directoriesOnly || S_ISDIR(sb.st_mode)) { resultList.add(new File(path)); } } catch (ErrnoException ignored) { } } }
上述方法不难看出只是把咱们传入的dex文件记录到List当中。返回继续查看makePathElements()方法:
private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) { List<Element> elements = new ArrayList<>(); /* * Open all files and load the (direct or contained) dex files * up front. */ for (File file : files) { File zip = null; File dir = new File(""); DexFile dex = null; String path = file.getPath(); String name = file.getName(); if (path.contains(zipSeparator)) { String split[] = path.split(zipSeparator, 2); zip = new File(split[0]); dir = new File(split[1]); } else if (file.isDirectory()) { // We support directories for looking up resources and native libraries. // Looking up resources in directories is useful for running libcore tests. elements.add(new Element(file, true, null, null)); } else if (file.isFile()) {//step this if (name.endsWith(DEX_SUFFIX)) {//直接为dex文件 // Raw dex file (not inside a zip/jar). try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException ex) { System.logE("Unable to load dex file: " + file, ex); } } else {//为jar或者apk文件 zip = file; try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException suppressed) { /* * IOException might get thrown "legitimately" by the DexFile constructor if * the zip file turns out to be resource-only (that is, no classes.dex file * in it). * Let dex == null and hang on to the exception to add to the tea-leaves for * when findClass returns null. */ suppressedExceptions.add(suppressed); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } if ((zip != null) || (dex != null)) { elements.add(new Element(dir, false, zip, dex)); } } return elements.toArray(new Element[elements.size()]); }
这里将传进来的file文件调用loadDexFile()加工成DexFile,这里假设咱们传进来的都是Dex文件,不是apk或者jar,因此只会走step this这一步的判断,接下来查看loadDexFile()方法:
private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { if (optimizedDirectory == null) {//optimizedDirectory传入是否为空 return new DexFile(file); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0); } }
这里能够看到若是咱们在optimizedDirectory传空值,则直接生成了一个DexFile,在DexFile的构造方法中执行了native方法,这里就到这里为止,native作的操做猜测是保存对应的Dex以及解析出Class。
回到makePathElements()方法中,loadDexFile()方法执行完毕后,向List中添加Element节点,到此makePathElements()就完成了。
关于native的list这里就不研究了,有兴趣的查看源码吧。
DexPathList的构造方法分析好了,而后继续回到对应的BaseDexClassLoader的findClass()方法中:
@Override 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; }
能够看到又回到了DexPathList中:
public Class findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } //DexFile.class public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, suppressed); } private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie);//调用native方法 } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; }
这里最终经过DexFile.defineClass()方法获取到Class对象,native怎么调用的咱们不关心,咱们能够知道在DexPathList初始化的时候会在native记录下dex中的Class,而后在调用BaseDexClassLoader.findClass()时候会从native取到对应的Class就行ok了。
研究完BaseDexClassLoader后,基本上PathClassLoader和DexClassLoader就差很少了,PathClassLoader继承自BaseDexClassLoader,简单实现了两个构造方法:
/** * Provides a simple {@link ClassLoader} implementation that operates on a list * of files and directories in the local file system, but does not attempt to * load classes from the network. Android uses this class for its system class * loader and for its application class loader(s). */ public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); } }
对于DexClassLoader而言,也实现了一个构造方法:
** * A class loader that loads classes from {@code .jar} and {@code .apk} files * containing a {@code classes.dex} entry. This can be used to execute code not * installed as part of an application. * * <p>This class loader requires an application-private, writable directory to * cache optimized classes. Use {@code Context.getDir(String, int)} to create * such a directory: <pre> {@code * File dexOutputDir = context.getDir("dex", 0); * }</pre> * * <p><strong>Do not cache optimized classes on external storage.</strong> * External storage does not provide access controls necessary to protect your * application from code injection attacks. */ public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }
能够看到两个不一样的ClassLoader惟一的区别在于传入的第二个参数是否为空,而上述分析BaseDexClassLoader也是假设optimizedDirectory的File==null
的状况,即分析了自己PathClassLoader的状况,那看来仍是得继续分析一下optimizedDirectory的File!=null
的状况:
//DexPathList.java private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { if (optimizedDirectory == null) { return new DexFile(file); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0); } } /** * Converts a dex/jar file path and an output directory to an * output file path for an associated optimized dex file. */ private static String optimizedPathFor(File path, File optimizedDirectory) { String fileName = path.getName(); if (!fileName.endsWith(DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath(); } static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { /* * TODO: we may want to cache previously-opened DexFile objects. * The cache would be synchronized with close(). This would help * us avoid mapping the same DEX more than once when an app * decided to open it multiple times. In practice this may not * be a real issue. */ return new DexFile(sourcePathName, outputPathName, flags); }
上述代码能够看到,经过optimizedDirectory
将全部的dex保存到了咱们指定的optimizedDirectory目录下,而后再生成一个DexFile。
整体来看来个ClassLoader的区别就在于去哪缓存咱们须要加载的dex文件,若是使用PathClassLoader则使用系统默认的文件夹,若是使用DexClassLoader则是使用咱们自定义的文件夹,DexClassLoader能够指定本身的optimizedDirectory,因此它能够加载外部的dex,由于这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,因此它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。
首先写个类:
package com.lin; public class Heelo { public String getHello() { return "Hello Dex"; } }
把这个类打包成jar,在eclipse中可使用export->Java->JAR File来导出jar包,而后经过dx命令把jar转换成dex,接下来就直接在代码中获取类便可:
private void testDex() { File file = new File(getPath() + File.separator + "sayhello_dex.jar"); if (!file.exists()) { Log.e("dasd", "dasd"); } DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null, getClassLoader()); try { Class clazz = classLoader.loadClass("com.lin.Heelo"); String text= (String) clazz.getMethod("getHello").invoke(clazz.newInstance(),null); Log.e("1111", text); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } //输出结果: E/1111: Hello Dex
资料参考:
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html
https://jaeger.itscoder.com/android/2016/08/27/android-classloader.html