Android進階知識:類加載相關

1. 前言

類加載原理做爲程序運行的基礎,一直在程序的背後默默的付出。現在Android中的插件化、熱修復等動態加載技術的實現也都涉及到了類加載的原理。關於類加載的相關知識我之前也是遇到一點看一點,沒有完整的詳細的瞭解過,最近有時間專門對這塊知識進行了學習,因而這裏作一個總結。java

2. 類加載過程

一個類從.class文件被加載到內存,到在內存中使用,最後從內存中卸載,這是一個完整的生命週期過程。不過在得到.class文件以前,咱們編碼時的文件格式仍是.java文件格式,還記得剛學Java時學到過在完成編碼以後要先執行javac命令進行編譯,編譯生成對應的.class文件,以後再經過java命令執行Java程序。不過當時只知道是先編譯再運行,並不知道究竟是怎麼運行的誰去運行的。程序員

其實一個類從.class文件被加載到內存到從內存中卸載,整個生命週期一共通過如下幾個階段:數組

  1. 加載
  2. 鏈接(包含驗證、準備、解析三個階段)
  3. 初始化
  4. 使用
  5. 卸載

類加載生命週期

2.1 加載階段

在加載階段虛擬機主要完成如下三件事情:安全

  1. 經過一個類的全限定名來獲取定義此類的二進制字節流。
  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。

這個簡單的來講加載階段主要就是將類的.class文件做爲二進制字節流讀入內存,而且在內存中實例化一個java.lang.Class對象以便後續訪問。在這個階段中.class文件二進制字節流的讀取來源沒有太多限制,能夠很是靈活。好比能夠從本地系統中讀取、能夠從jar包中讀取、能夠從網絡下載等等。bash

2.2 鏈接—驗證階段

驗證是鏈接階段中的第一步,主要的做用是保證Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機的自身安全。在驗證階段大體會完成如下四個檢驗操做:markdown

  1. 文件格式驗證:驗證字節流是否符合Class文件格式的規範,而且能被當前版本的虛擬機處理。
  2. 元數據驗證:對字節碼描述的信息進行語義分析,保證其描述符合Java語言規範的要求。
  3. 字節碼驗證:經過數據流和控制流分析,肯定程序邏輯是合法的、符合羅急的。
  4. 符號引用驗證:對類自身之外的信息進行匹配性校驗。

從以上幾個操做能夠看出,這個階段主要就是將二進制字節流進行一個合法驗證,包括文件格式、語義、數據流控制流和符號引用等。保證不會出現相似文件格式錯誤、繼承了被final修飾的類、指令跳轉錯誤、類型轉換錯誤、修飾符訪問性等等錯誤狀況。cookie

2.3 鏈接—準備階段

準備階段中主要是爲類中靜態變量在方法區裏分配內存而且設置類變量的初始值。這裏的初始值即零值。具體以下:網絡

數據類型 零值
int 0
long 0L
short (short)0
char '\u0000'
byte (byte)0
boolean false
float 0.0f
double 0.0d
reference null

2.4 鏈接—解析階段

解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調試限定符7類符號引用進行。數據結構

2.5 初始化階段

初始化階段中真正開始執行類中定義的Java程序代碼,爲全部類變量進行賦值,執行靜態代碼塊。app

3. Java中類加載器

從上面的類加載過程能夠看出在初始化階段之前除了加載階段,其他階段都是由虛擬機主導控制。而在加載階段能夠經過自定義類加載器進行參與。類加載器顧名思義是用來加載類的,負責將類的.class文件轉換成內存中類的二進制字節流。Java中的類加載器按類型分有兩種:系統類加載器和自定義類加載器。其中系統類加載器有主要有三種,分別是:引導類加載器(Bootstrap ClassLoader)、拓展類加載器(Extensions ClassLoader)和應用程序類加載器(Application ClassLoader)。

3.1 系統類加載器

3.1.1 引導類加載器(Bootstrap ClassLoader

這個類加載器是用C/C++語言實現的,用來加載JDK中的核心類,主要加載$JAVA_HOME/jre/lib目錄下的類,例如rt.jarresources.jar等包中的類。

3.1.2 拓展類加載器(Extensions ClassLoader

這個類加載器是用Java語言實現的,實現類爲ExtClassLoader,用來加載Java的拓展類,主要加載$JAVA_HOME/jre/lib/ext目錄和系統屬性java.ext.dir所指定的目錄。

3.1.3 應用程序類加載器(Application ClassLoader

這個類加載器是用Java語言實現的,實現類爲AppClassLoader,能夠經過ClassLoader.getSystemClassLoader方法獲取到,主要加載Classpath目錄和系統屬性java.class.path指定的目錄下的類。

3.1.4 ClassLoader的繼承關係
public class ClassLoaderDemo {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        while (classLoader != null) {
            System.out.println("ClassLoader:" + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}
複製代碼

這裏新建一個JavaClassLoaderDemo,循環打印其類加載器和父類加載器,控制檯輸出結果以下。

從結果能夠看出ClassLoaderDemo類的類加載器是AppClassLoaderAppClassLoader的父類加載器是ExtClassLoader,而ExtClassLoader的父類加載器就爲null了,這是由於ExtClassLoader的父類加載器BootstrapClassLoader是由C/C++語言實現的,因此在Java中沒法獲取到它的引用。接下來再進入源碼來看一下,先看AppClassLoader

static class AppClassLoader extends URLClassLoader {
	......
}
複製代碼

查看源碼發現AppClassLoader的父類並非ExtClassLoader而是URLClassLoader,再看ExtClassLoader

static class ExtClassLoader extends URLClassLoader {
    ......
}
複製代碼

ExtClassLoader的父類也是URLClassLoader進而再看URLClassLoader

public class URLClassLoader extends SecureClassLoader implements Closeable {
	......
}

public class SecureClassLoader extends ClassLoader {
	......
}

public abstract class ClassLoader {
	......
}
複製代碼

URLClassLoader的父類是SecureClassLoader,而SecureClassLoader的父類是ClassLoaderClassLoader是一個抽象類。經過對源碼的跟蹤發現,彷佛這裏的繼承關係與控制檯輸出的結果不太一致。因而進一步去看輸出ClassLoaderDemo中調用的ClassLoader.getParent()方法源碼。

public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }
     // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
複製代碼

ClassLoadergetParent方法中看到返回的是一個成員變量中的parent,他是一個ClassLoader類型對象。繼續跟蹤尋找它是在哪裏初始化賦值的。

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
複製代碼

跟蹤查看源碼發現父類加載器parent是在ClassLoader的構造函數時傳入的,若是沒有傳入默認調用getSystemClassLoader方法獲取一個父類加載器。接下來繼續查看getSystemClassLoader方法。

public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
複製代碼

getSystemClassLoader方法中又調用了initSystemClassLoader方法初始化系統類加載器,方法最後將這個類加載器scl返回。繼續查看initSystemClassLoader方法。

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
      .......
}
複製代碼

這個方法中獲取到Launcher以後調用了LaunchergetClassLoader方法獲取到建立的類加載器,因而再到Launcher中查看。

public ClassLoader getClassLoader() {
        return this.loader;
}

複製代碼

LaunchergetClassLoader方法中返回了其成員變量中的loader對象,因而再去尋找這個對象的建立。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
	......
}

複製代碼

Launcher的構造函數裏找到,這裏是經過Launcher.AppClassLoader.getAppClassLoader(var1)方法建立的loader,因而再進入查看。

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
}

複製代碼

getAppClassLoader方法中最終new了一個AppClassLoader返回,這就又回到了AppClassLoader的構造方法。以前看過構造傳入的第二個參數就是parent父類加載器,這裏傳入的var0能夠看到就是ExtClassLoader類型。

總結來講ClassLoader的父子關係並非由繼承實現的,AB的父類加載器,並不表示B繼承了A,每一個CLassLoader中保存了一個它的父類加載器的引用,經過getParent方法得到的就是它的值,它是在類加載器建立時構造函數中進行賦值的。若是構造中沒有傳入父類加載器默認調用getSystemClassLoader方法獲取系統類加載器,經過查看發現默認系統類加載器就是AppClassLoader。在Launcher的構造方法中,會依次建立ExtClassLoaderAppClassLoader,此時ExtClassLoader做爲父類加載器由構造函數傳入AppClassLoader。實際的類加載繼承關係以下圖。

Java類加載器

3.2 自定義類加載器

在一些特殊需求場景下可能須要程序員自定義類加載器。例如從網絡下載一個加密過的.class類文件,此時就須要自定義類加載器,先進行文件解密再進行類加載。下面就來模擬一下這個例子,先定義一個測試類TestPrint

public class TestPrint {
    public void printString() {
        System.out.println("測試輸出字符串");
    }
}
複製代碼

使用javac命令編譯生成TestPrint.class文件。

再寫個加密文件方法,這裏加密就用簡單使用下 Base64加密,將編譯生成的 .class文件轉成二進制字節流加密後再保存成本地文件。

public class Test {
    public static void main(String[] args) {
        byte[] classBytes = FileIOUtils.readFile2BytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class");
        FileIOUtils.writeFileFromBytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class",Base64.getEncoder().encode(classBytes));
}
}
複製代碼

獲得加密後的TestPrint.class後接下來編寫自定義的類加載器MyClassLoader

public class MyClassLoader extends ClassLoader {
    private String path;
    protected MyClassLoader(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 獲取二進制字節流
        byte[] classBytes = loadClassBytes(name);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
    }
    private byte[] loadClassBytes(String name) {
        String fileName = getFileName(name);
        File file = new File(path, fileName);
        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(file);
            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;

            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    private String getFileName(String name) {
        String fileName;
        int index = name.lastIndexOf(".");
        if (index == -1) {
            fileName = name + ".class";
        } else {
            fileName = name.substring(index + 1) + ".class";
        }
        return fileName;
    }
}


複製代碼

自定義類加載器分爲如下幾個步驟:

  1. 繼承抽象類ClassLoader
  2. 複寫findClass方法。
  3. findClass方法中獲取到二進制字節流後,調用defineClass方法。

MyClassLoaderfindClass方法中首先讀取到本地硬盤下的TestPrint.class文件的字節流,而後調用defineClass方法,該方法會將字節流轉化爲Class類型。最後寫一個測試類調用。

public class TestMyClassLoader {
    public static void main(String[] args) {
        // 初始化類加載器
        MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader");
        try {
            // 使用自定義類加載器獲取Class
            Class<?> printTest = myClassLoader.loadClass("TestPrint");
            // 建立實例
            Object instance = printTest.newInstance();
            System.out.println("classloader:" + instance.getClass().getClassLoader());
            Method method = printTest.getDeclaredMethod("printString", null);
            method.setAccessible(true);
            method.invoke(instance, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

複製代碼

運行結果:

此時發生錯誤是由於作了 Base64加密,須要在 defineClass前進行一個解碼操做,修改 findClass方法。

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 獲取二進制字節流
        byte[] classBytes = loadClassBytes(name);
        // Base64 decode
        classBytes = Base64.getDecoder().decode(classBytes);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
}

複製代碼

再次運行查看結果:

此時程序就能正常的執行,調用類中的方法了。

4. 雙親委託

4.1 雙親委託模式

學習類加載器就避免不了要了解雙親委託,它是類加載器尋找加載類的模式。仍是先看到以前自定義類加載器的例子,自定義時複寫了findClass方法,可是使用時卻沒有直接調用這個方法,使用時是經過ClassLoader.loadClass方法得到Class的。雙親委託模式就是在這個方法中實現的,因而進入查看源碼。

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先檢查該類是否加載過
            Class<?> c = findLoadedClass(name);
            // c爲空說明沒有加載過
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	// 判斷是否有父類加載器
                    if (parent != null) {
                      	// 有父類加載器就調用父類加載器的loadClass方法
                        c = parent.loadClass(name, false);
                    } else {
                    	// 沒有父類加載器就調用這個方法
                    	// 方法中會調用native方法findBootstrapClass使用BootstrapClassLoader檢查該類是否已加載
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
			   // 走到此處c還爲null說明父類加載器沒有加載該類
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 就調用自身的findClass查找加載該類
                    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中的代碼邏輯很是清晰的描述了雙親委託模式,首先經過findLoadedClass方法檢驗要加載的類是否被加載過。加載過直接返回該類的Class對象,沒加載過則c爲空進入下面的判斷,判斷父類加載器parent是否爲空,不爲空調用父類的loadClass方法,爲空則調用findBootstrapClassOrNull方法,該方法中會繼續調用native方法findBootstrapClass使用BootstrapClassLoader檢查該類是否已加載。接着若是沒有加載該類就會調用自身的findClass方法查找加載該類。

最後對雙親委託模式作個總結:雙親委託模式是指類加載器加載一個類首先是判斷這個類是否加載過,沒加載過不會本身直接加載,而是委託給其父類加載器去查找,一直委託到頂層引導類加載器BootstrapClassLoader,若是BootstrapClassLoader找到該Class就會直接返回,沒找到就會交給子類加載器依次向下查找,一直沒找到最後就會交給自身去查找。

雙親委託模式

4.2 雙親委託模式的好處

雙親委託模式有兩個好處:

  1. 避免了類的重複加載,一個類若是加載過一次就不須要再次加載了,而是直接讀取已經加載的Class
  2. 類隨着它的類加載器一塊兒具有了一種帶優先級的層次關係,使得更加安全。例如加載java.lang.Object類不管使用哪一個類加載器加載,最終都會委託給頂層BootstrapClassLoader來加載,這樣保證了Object類永遠是同一個類,不會出現多個不一樣的Object類。這樣也沒法經過自定義一個Object類替換系統原來的Object類。

另外Java虛擬機判斷兩個類是同一個類,是依據兩個類類名一致,而且被同一個類加載器加載。這裏能夠在以前的自定義類加載器的基礎上測試下。將原來的MyClassLoader複製後重命名YourClassLoader一份,再編寫一個測試類。

public class TestDifferentClassLoader {
    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader/");
        YourClassLoader yourClassLoader = new YourClassLoader("/Users/sy/Downloads/ClassLoader/");
        try {
            Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
            // 使用不一樣的類加載器
            Class<?> yourPrintTest = yourClassLoader.loadClass("TestPrint");

            Object myInstance = myPrintTest.newInstance();
            Object yourInstance = yourPrintTest.newInstance();

            System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
            System.out.println(myInstance.getClass().getName());
            System.out.println(yourInstance.getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

複製代碼

運行結果:

修改一下用同一個類加載器。

public class TestDifferentClassLoader {
    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("D:\\");
        YourClassLoader yourClassLoader = new YourClassLoader("D:\\");
        try {
            Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
            // 用同一個類加載器
            Class<?> yourPrintTest = myClassLoader.loadClass("TestPrint");

            Object myInstance = myPrintTest.newInstance();
            Object yourInstance = yourPrintTest.newInstance();

            System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
            System.out.println(myInstance.getClass().getName());
            System.out.println(yourInstance.getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

5. Android中類加載器

Android中類加載器與Java中的類加載器相似可是並不徹底相同。Java中類加載器是加載.class文件,而Android中是加載的dex文件,不過Android中也分系統類加載器和自定義類加載器,系統類加載器主要包括如下三種:BootClassLoaderDexClassLoaderPathClassLoader。這裏仍是先循環打印一下Android中的父類加載器。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d("classLoader", "name:" + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}

複製代碼

運行日誌結果:

從日誌能夠看到這裏有兩個類加載器,一個是BootClassLoader另外一個是PathClassLoaderAndroid中的每一個類加載器裏一樣保存一個父類加載器的引用,getParent方法獲取到的一樣是這個ClassLoader,仍是先來看一下Android中的ClassLoader類。

public abstract class ClassLoader {
    private final ClassLoader parent;
		......
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
		
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 檢查該類是否已經加載
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        // 父類加載器不爲空則調用其的loadClass方法
                        c = parent.loadClass(name, false);
                    } else {
                    	// 父類加載器爲空就調用findBootstrapClassOrNull方法
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 父類加載器沒有查找到該類,則調用自身findClass方法查找加載
                    c = findClass(name);
                }
            }
            return c;
    }
    
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }
    public final ClassLoader getParent() {
        return parent;
    }
    ......
}
複製代碼

Android中的ClassLoader類一樣是一個抽象類,它的loadClass方法中的邏輯和Java中的相似,一樣是遵循了雙親委託模式,父類加載器不爲空則調用父類加載器的loadClass方法,爲空則調用findBootstrapClassOrNull方法,該方法這裏直接返回的null。若父類加載器沒有查找到須要加載的類,則調用自身的findClass方法。

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}
複製代碼

接着看到ClassLoader中的findClass裏是直接拋出了一個ClassNotFoundException異常,說明這個方法須要子類來實現。那麼接下來就來看看Android中類加載器的實際繼承關係。

抽象類ClassLoader定義了類加載器的主要功能,它的子類如上圖。先根據源碼梳理下它們之間的關係。

public class BaseDexClassLoader extends ClassLoader {
		......
}
public class SecureClassLoader extends ClassLoader {
		......
}
public class DexClassLoader extends BaseDexClassLoader {
		......
}
public class PathClassLoader extends BaseDexClassLoader {
		......
}
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
  	......
}
public class URLClassLoader extends SecureClassLoader implements Closeable {
  	......
}
public final class DelegateLastClassLoader extends PathClassLoader {
  	......
}

複製代碼

除了這些子類,抽象類ClassLoader中還有一個內部類BootClassLoader

class BootClassLoader extends ClassLoader {
		......
}

複製代碼

綜上所述Android中類加載器的繼承關係以下圖。

Android中類加載器

接下來簡單瞭解一下這些類加載器。

5.1 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);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }
		 ......
    @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }
        return clazz;
    }
  	......
}

複製代碼

BootClassLoaderClassLoader的內部類繼承自ClassLoader,而且提供了一個獲取單例的getInstance方法。與Java中不一樣BootClassLoader不是用C/C++實現的是用Java實現的,Android系統啓動時會使用BootClassLoader來預加載經常使用類。

5.2 DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

複製代碼

DexClassLoader看名字就知道是用來加載dex文件的,它的構造函數中有四個參數:

  • dexPath:表示dex相關文件路徑集合。
  • optimizedDirectory:表示解壓的dex文件存儲路徑。
  • librarySearchPath:包含C/C++庫的路徑集合。
  • parent:父類加載器。

DexClassLoader繼承自BaseDexClassLoader其主要的方法都在其父類中實現。

5.3 PathClassLoader

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);
    }
}

複製代碼

PathClassLoader是用來加載系統類和應用程序的類,一樣繼承自BaseDexClassLoader類, 它的構造函數裏沒有optimizedDirectory參數,一般用來加載已經安裝的dex文件。

5.4 BaseDexClassLoader

BaseDexClassLoaderDexClassLoaderPathClassLoader的父類。

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    ......
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);

        if (reporter != null) {
            reporter.report(this.pathList.getDexPaths());
        }
    }
		......	
    @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對象,接着它的findClass方法中是經過pathList.findClass方法得到的Class對象。

public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
}

複製代碼

findClass方法中循環遍歷了dexElements數組,再經過Element.findClass方法來得到Class對象,Element又是DexPathList的內部類,進一步找到它的findClass方法查看。

public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
}

複製代碼

這個方法中又調用了DexFileloadClassBinaryName方法。

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;
}

複製代碼

loadClassBinaryName方法裏繼續調用了defineClass方法,而defineClass方法裏最終調用了defineClassNative這個native方法加載dex文件。

Android系統啓動後init進程會啓動Zygote進程,Zygote進程初始化時會預加載經常使用類,進而會調用Class.forName方法,該方法裏會經過BootClassLoader.getInstance方法建立BootClassLoader。以後Zygote進程又會啓動SystemServer進程,進而又會經過PathClassLoaderFactory建立PathClassLoader。總而言之,Android中的這兩個類加載器在啓動時就進行初始化建立了。

6. 總結

  • Java中類加載生命週期包括:加載、鏈接、初始化、使用、卸載這些階段,其中鏈接階段又分爲驗證、準備、解析。
  • Java中的 系統類加載器與Android中的系統類加載器並不相同,但都遵循了雙親委託模式。
  • 如有特殊需求須要自定義類加載器,能夠經過繼承ClassLoader類複寫它的findClass方法實現本身的類加載器。

7. 參考資料

深刻理解Java 虛擬機

Android進階解密

相關文章
相關標籤/搜索