Android的Dalvik/ART虛擬機如同標準JAVA的JVM虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。所以,咱們能夠利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載可執行文件的目的。Dalvik/ART虛擬機雖然與JVM虛擬機不同,ClassLoader具體的加載細節不同,可是工做機制是相似的,也就是說在Android中一樣能夠採用相似的動態加載插件的功能.java
下面咱們簡單寫一個Demo來看下ClassLoader有哪些 源碼以下:android
package org.professor.classloaderdemo; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; public class MainActivity extends AppCompatActivity { @ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("TAG", "Context的類加載加載器:" + Context.class.getClassLoader()); Log.i("TAG", "ImageView的類加載器:" + ImageView.class.getClassLoader()); Log.i("TAG", "應用程序默認加載器:" + getClassLoader()); Log.i("TAG", "系統類加載器:" + ClassLoader.getSystemClassLoader()); Log.i("TAG", "系統類加載器和Context的類加載器是否相等:" + (Context.class.getClassLoader() == ClassLoader.getSystemClassLoader())); //false Log.i("TAG", "系統類加載器和應用程序默認加載器是否相等:" + (getClassLoader() == ClassLoader.getSystemClassLoader())); Log.i("TAG", "打印應用程序默認加載器的委派機制:"); ClassLoader classLoader = getClassLoader(); while (classLoader != null) { Log.i("TAG", "類加載器:" + classLoader); classLoader = classLoader.getParent(); } Log.i("TAG", "打印系統加載器的委派機制:"); classLoader = ClassLoader.getSystemClassLoader(); while (classLoader != null) { Log.i("TAG", "類加載器:" + classLoader); classLoader = classLoader.getParent(); } }
LOGTAG_INFO以下:c++
08-26 16:08:45.688 12403-12403/? I/TAG: Context的類加載加載器:java.lang.BootClassLoader@b039c3e 08-26 16:08:45.688 12403-12403/? I/TAG: ImageView的類加載器:java.lang.BootClassLoader@b039c3e 08-26 16:08:45.688 12403-12403/? I/TAG: 應用程序默認加載器:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.lq.classloaderdemo-1/base.apk"],nativeLibraryDirectories=[/data/app/com.lq.classloaderdemo-1/lib/arm64, /vendor/lib64, /system/lib64]]] 08-26 16:08:45.688 12403-12403/? I/TAG: 系統類加載器:dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]] 08-26 16:08:45.688 12403-12403/? I/TAG: 系統類加載器和Context的類加載器是否相等:false 08-26 16:08:45.688 12403-12403/? I/TAG: 系統類加載器和應用程序默認加載器是否相等:false 08-26 16:08:45.688 12403-12403/? I/TAG: 打印應用程序默認加載器的委派機制: 08-26 16:08:45.688 12403-12403/? I/TAG: 類加載器:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.lq.classloaderdemo-1/base.apk"],nativeLibraryDirectories=[/data/app/com.lq.classloaderdemo-1/lib/arm64, /vendor/lib64, /system/lib64]]] 08-26 16:08:45.688 12403-12403/? I/TAG: 類加載器:java.lang.BootClassLoader@b039c3e 08-26 16:08:45.688 12403-12403/? I/TAG: 打印系統加載器的委派機制: 08-26 16:08:45.688 12403-12403/? I/TAG: 類加載器:dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]] 08-26 16:08:45.688 12403-12403/? I/TAG: 類加載器:java.lang.BootClassLoader@b039c3e
由上知 類加載器有以下幾種git
1.BootClassLoader數組
能夠看出View 和Context 都是由該ClassLoader 加載的,系統類加載器cookie
由源碼得知 該類也是繼承了ClassLoaderapp
2.默認類加載器是PathClassLoader,同時能夠看到加載的apk路徑,libPath(通常包括/vendor/lib和/system/lib)ide
3.系統類加載器 注意區分系統類的加載器優化
系統類加載器其實仍是PathClassLoader,只是加載的apk路徑不是/data/app/xxx.apk了,而是系統apk的路徑:/system/app/xxx.apkui
4.從上面看出,系統加載器和默認加載器的委派關係:其基類都是BootClassLoader
UML 圖
ClassLoader.java 源碼
從源碼能夠看出ClassLoader主要就是傳入一個父構造器,並且通常父構造器不能爲空,不像java虛擬機裏父構造器爲空時默認的父構造器爲Bootstrap ClassLoader。Android中默認無父構造器傳入的狀況下,默認父構造器爲一個PathClassLoader且此PathClassLoader父構造器爲BootClassLoader。
public abstract class ClassLoader { static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } private ClassLoader parent; private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); return new PathClassLoader(classPath, BootClassLoader.getInstance()); //getSystemClassLoader加載器是pathclassloader,它的parent是BootClassLoader,可是DexPathList[[directory "."].. } public static ClassLoader getSystemClassLoader() { return SystemClassLoader.loader; //返回系統默認類加載器 } protected ClassLoader() { this(getSystemClassLoader(), false); } ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { if (parentLoader == null && !nullAllowed) { throw new NullPointerException("parentLoader == null && !nullAllowed"); } parent = parentLoader; } //自定義classloader須要重載該方法 protected Class<?> findClass(String className) throws ClassNotFoundException { throw new ClassNotFoundException(className); } protected final Class<?> findLoadedClass(String className) { ClassLoader loader; if (this == BootClassLoader.getInstance()) //若是該classloader是BootClassLoader類型 loader = null; else loader = this; return VMClassLoader.findLoadedClass(loader, className); //調用本地c/c++方法 } //能夠看到android系統其實也實現了雙親委託模型,只是跟java的雙親委託模型有點不一樣而已 protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className); //檢查是否已經加載過 if (clazz == null) { ClassNotFoundException suppressed = null; try { clazz = parent.loadClass(className, false); //使用parent去查找 } catch (ClassNotFoundException e) { suppressed = e; } if (clazz == null) { try { clazz = findClass(className); //調用findclass } catch (ClassNotFoundException e) { e.addSuppressed(suppressed); throw e; } } } return clazz; } //能夠看到loadClass的resolve參數是沒用的 protected final void resolveClass(Class<?> clazz) { } } //BootClassLoader單例模型 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; } public BootClassLoader() { super(null, true); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false, null); } }
NOTE**: 雙親委派模型**
這和java虛擬機中常見的雙親委派模型一致的,這種模型並非一個強制性的約束模型,好比你能夠繼承ClassLoader複寫loadCalss方法來破壞這種模型,只不過雙親委派模是一種被推薦的實現類加載器的方式,並且jdk1.2之後已經不提倡用戶在覆蓋loadClass方法,而應該把本身的類加載邏輯寫到findClass中。
BootClassLoader
和java虛擬機中不一樣的是BootClassLoader是ClassLoader內部類,由java代碼實現而不是c++實現,是Android平臺上全部ClassLoader的最終parent,這個內部類是包內可見,因此咱們無法使用。
DexClassLoader&PathClassLoader
DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }
PathClassLoader.java
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); } }
NOTE: 我這將一些亂七八糟的註釋已經去掉了,這樣看起來比較明顯一些。其實就是對器基類BaseDexClassLoader進行一個簡單的封裝,真正的處理邏輯就是在BaseDexClassLoader裏面
BaseDexClassLoader.java
上面能夠看到 DexClassLoader 和 PathClassLoader 都是對該類進行了一個封裝
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; }
能夠看到 該類 實現了ClassLoader 而且重寫了findClass() 方法, 先解釋一下,構造器裏面四個參數的含義
dexPath,指目標類所在的APK或jar文件的路徑,類裝載器將從該路徑中尋找指定的目標類,該類必須是APK或jar的全路徑.若是要包含多個路徑,路徑之間必須使用特定的分割符分隔,特定的分割符可使用System.getProperty(「path.separtor」)得到。上面"支持加載APK、DEX和JAR,也能夠從SD卡進行加載"指的就是這個路徑,最終作的是將dexPath路徑上的文件ODEX優化到內部位置optimizedDirectory,而後,再進行加載的。
File optimizedDirectory,因爲dex文件被包含在APK或者Jar文件中,所以在裝載目標類以前須要先從APK或Jar文件中解壓出dex文件,該參數就是制定解壓出的dex 文件存放的路徑。這也是對apk中dex根據平臺進行ODEX優化的過程。其實APK是一個程序壓縮包,裏面包含dex文件,ODEX優化就是把包裏面的執行程序提取出來,就變成ODEX文件,由於你提取出來了,系統第一次啓動的時候就不用去解壓程序壓縮包的程序,少了一個解壓的過程。這樣的話系統啓動就加快了。爲何說是第一次呢?是由於DEX版本的也只有第一次會解壓執行程序到 /data/dalvik-cache(針對PathClassLoader)或者optimizedDirectory(針對DexClassLoader)目錄,以後也是直接讀取目錄下的的dex文件,因此第二次啓動就和正常的差很少了。固然這只是簡單的理解,實際生成的ODEX還有必定的優化做用。ClassLoader只能加載內部存儲路徑中的dex文件,因此這個路徑必須爲內部路徑。
libraryPath,指目標類中所使用的C/C++庫存放的路徑
ClassLoader parent,是指該裝載器的父裝載器,通常爲當前執行類的裝載器,例如在Android中以context.getClassLoader()做爲父裝載器。
因而可知,DexClassLoader能夠加載任何路徑的apk/dex/jar(DexPathList中體現) PathClassLoader只能加載/data/app中的apk,也就是已經安裝到手機中的apk。這個也是PathClassLoader做爲默認的類加載器的緣由,由於通常程序都是安裝了,在打開,這時候PathClassLoader就去加載指定的apk(解壓成dex,而後在優化成odex)就能夠了。
DexPathList.java
該類有一個Element[] 數組,該類存放全部的dex文件 主要代碼以下:
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists()) { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); if (suppressedExceptions.size() > 0) { this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); } else { dexElementsSuppressedExceptions = null; } this.nativeLibraryDirectories = splitLibraryPath(libraryPath); } //該構造器中會調用 /** * Makes an array of dex/resource path elements, one per element of * the given array. */ private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { ArrayList<Element> elements = new ArrayList<Element>(); /* * Open all files and load the (direct or contained) dex files * up front. */ for (File file : files) { File zip = null; DexFile dex = null; String name = file.getName(); if (file.isDirectory()) { // We support directories for looking up resources. // This is only useful for running libcore tests. elements.add(new Element(file, true, null, null)); } else if (file.isFile()){ if (name.endsWith(DEX_SUFFIX)) { // 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 { 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(file, false, zip, dex)); } } return elements.toArray(new Element[elements.size()]); } //而後去loadDexFile 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); } }
這能夠看到當optimizedDirectory==null時,去實例化一個DexFile,也就是說經過PathClassLoader去加載時最終去new 一個DexFile(),而當使用DexClassLoader 時,會去調用DexFile的一個static 方法去加載.
DexFile.java
public DexFile(String fileName) throws IOException { mCookie = openDexFile(fileName, null, 0); mFileName = fileName; guard.open("close"); //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName); } private DexFile(String sourceName, String outputName, int flags) throws IOException { if (outputName != null) { try { String parent = new File(outputName).getParent(); if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { throw new IllegalArgumentException("Optimized data directory " + parent + " is not owned by the current user. Shared storage cannot protect" + " your application from code injection attacks."); } } catch (ErrnoException ignored) { // assume we'll fail with a more contextual error later } } mCookie = openDexFile(sourceName, outputName, flags); mFileName = sourceName; guard.open("close"); //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); } static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { return new DexFile(sourcePathName, outputPathName, flags); } public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, suppressed); } private static Class defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie); } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; } private static native Class defineClassNative(String name, ClassLoader loader, long cookie) throws ClassNotFoundException, NoClassDefFoundError;
能夠看到靜態方法去loadDex時候,也是去new 一個DexFile
咱們如今梳理一下整個過程
Note: 以上源碼在Android 5.1中