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