基本知識
Java的類加載設計了一套雙親代理的模式,使得用戶沒法替換系統的核心類,從而讓應用更安全。所謂雙親代理就是指,當加載類的時候首先去Bootstrap中加載類,如果沒有則去Extension中加載,如果再沒有才去AppClassLoader中去加載。從而實現安全和穩定。
Java ClassLoader
BootstrapClassLoader
引導類加載器 ,用來加載Java的核心庫。通過底層代碼來實現的,基本上只要parent為null,那就表示引導類加載器。
比如:charsets.jar、deploy.jar、javaws.jar、jce.jar、jfr.jar、jfxswt.jar、jsse.jar、management-agent.jar、plugin.jar、resources.jar、rt.jar
ExtClassLoader
拓展類加載器 ,用來加載Java的拓展的類庫, ${JAVA_HOME}/jre/lib/ext/ 目錄中的所有jar。
比如:cldrdata.jar、dnsns.jar、jfxrt.jar、localedata.jar、nashorn.jar、sunec.jar、sunjce_provider.jar、sunpkcs11.jar、zipfs.jar等等
AppClassLoader
系統類加載器 (不要被名字給迷惑),用來加載Java應用中的類。一般來說自己寫的類都是通過這個加載的。而Java中 ClassLoader.getSystemClassLoader() 返回的就是AppClassLoader。(Android中修改了ClassLoader的邏輯,返回的會是一個PathClassLoader)
自定義ClassLoader
用戶如果想自定義ClassLoader的話,只需要繼承自 java.lang.ClassLoader 即可。
ClassLoader中與加載類相關的方法:
也許你不太了解上面幾個函數的區別,沒關系,我們來看下源碼是如何實現的。
//ClassLoader.javaprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 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 } } return c;}所以優先級大概如下:
loadClass → findLoadedClass → parent.loadClass/findBootstrapClassOrNull → findClass → defineClass
Android ClassLoader
在Android中ClassLoader主要有兩個直接子類,叫做 BaseDexClassLoader 和 SecureClassLoader 。而前者有兩個直接子類是 PathClassLoader 和 DexClassLoader (Android O添加了 InMemoryDexClassLoader ,略)。
我們只討論PathClassLoader和DexClassLoader
PathClassLoader
用來加載安裝了的應用中的dex文件。它也是Android里面的一個最核心的ClassLoader了。相當于Java中的那個AppClassLoader。
public class PathClassLoader extends BaseDexClassLoader { /** * Creates a {@code PathClassLoader} that operates on a given list of files * and directories. This method is equivalent to calling * {@link #PathClassLoader(String, String, ClassLoader)} with a * {@code null} value for the second argument (see description there). * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param parent the parent class loader */ public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } /** * Creates a {@code PathClassLoader} that operates on two given * lists of files and directories. The entries of the first list * should be one of the following: * * <ul> * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as * well as arbitrary resources. * <li>Raw ".dex" files (not inside a zip file). * </ul> * * The entries of the second list should be directories containing * native library files. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param librarySearchPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); }} 它的實例化是通過調用 ApplicationLoaders.getClassLoader 來實現的。
它是在ActivityThread啟動時發送一個BIND_APPLICATION消息后在handleBindApplication中創建ContextImpl時調用LoadedApk里面的 getResources(ActivityThread mainThread) 最后回到ActivityThread中又調用LoadedApk的 getClassLoader 生成的,具體的在LoadedApk的 createOrUpdateClassLoaderLocked 。
那么問題來了,當Android加載class的時候,LoadedApk中的ClassLoader是怎么被調用到的呢?
其實Class里面,如果你不給ClassLoader的話,它默認會去拿Java虛擬機棧里面的 CallingClassLoader ,而這個就是LoadedApk里面的同一個ClassLoader。
//Class.javapublic static Class<?> forName(String className) throws ClassNotFoundException { return forName(className, true, VMStack.getCallingClassLoader());} 查看VMStack的源碼發現 getCallingClassLoader 其實是一個native函數,Android通過底層實現了這個。
//dalvik.system.VMStack/** * Returns the defining class loader of the caller's caller. * * @return the requested class loader, or {@code null} if this is the * bootstrap class loader. */@FastNativenative public static ClassLoader getCallingClassLoader();底層想必最終也是拿到LoadedApk里面的ClassLoader。
DexClassLoader
它是一個可以用來加載包含dex文件的jar或者apk文件的,但是它可以用來加載非安裝的apk。比如加載sdcard上面的,或者NetWork的。
public class DexClassLoader extends BaseDexClassLoader { /** * Creates a {@code DexClassLoader} that finds interpreted and native * code. Interpreted classes are found in a set of DEX files contained * in Jar or APK files. * * <p>The path lists are separated using the character specified by the * {@code path.separator} system property, which defaults to {@code :}. * * @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; must not be {@code null} * @param librarySearchPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); }}比如現在很流行的插件化/熱補丁,其實都是通過DexClassLoader來實現的。具體思路是: 創建一個DexClassLoader,通過反射將前者的DexPathList跟系統的PathClassLoader中的DexPathList合并,就可以實現優先加載我們自己的新類,從而替換舊類中的邏輯了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答