Android中的ClassLoader分析

前言

這篇文章主要是講解Android中的ClassLoader


Dalvik VM

Dalvik是Google公司自己設(shè)計(jì)用于Android平臺(tái)的Java虛擬機(jī)。它可以支持已轉(zhuǎn)換為.dex(即Dalvik Executable)格式的Java應(yīng)用程序的運(yùn)行,.dex格式是專為Dalvik設(shè)計(jì)的一種壓縮格式,可以減少整體文件尺寸,提高I/o操作的類查找速度所以適合內(nèi)存和處理器速度有限的系統(tǒng)。

關(guān)于Dalvik更多內(nèi)容參考這篇文章:Dalvik概述
說(shuō)的直白一點(diǎn)就是針對(duì)Android而生的JVM的升級(jí)。他與JVM的不同:

clipboard.png

第一點(diǎn)不用解釋
第二點(diǎn)后面會(huì)詳細(xì)講解
第三點(diǎn):JVM只存在一個(gè),DVM 可以存在多個(gè),某一個(gè)引用程序掛掉以后不會(huì)影響的其他程序,保證程序的穩(wěn)定性。
第四點(diǎn):基于棧 表示方法的調(diào)用時(shí)在棧中完成的 寄存器運(yùn)行更快


ART與Dalvik的不同
ART模式英文全稱為:Android runtime,谷歌Android 4.4系統(tǒng)新增的一種應(yīng)用運(yùn)行模式,與傳統(tǒng)的Dalvik模式不同,ART模式可以實(shí)現(xiàn)更為流暢的安卓系統(tǒng)體驗(yàn),對(duì)于大家來(lái)說(shuō),只要明白ART模式可讓系統(tǒng)體驗(yàn)更加流暢,不過(guò)只有在安卓4.4以上系統(tǒng)中采用此功能。這里只做簡(jiǎn)單介紹。

clipboard.png

現(xiàn)在市面的手機(jī)基本都是ART模式了。


Android中的ClassLoader

JVM的類加載器是將字節(jié)碼文件通過(guò)讀取后加載到JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)。而Android中的ClassLoader的作用是一樣的,只不過(guò)是加載到Dalvik中。

  • Android中的ClassLoader有哪些?

Android中的ClassLoader由一下4個(gè)類組成。

圖片.png

1:加載Framework層字節(jié)碼文件
2:加載已經(jīng)安裝到系統(tǒng)中APK文件中的字節(jié)碼文件(sdk中的文件)
3:加載指定目錄中的字節(jié)碼文件(如lib引入的jar中的文件等)
4:是2.3的父類

從上面我們知道一個(gè)應(yīng)用的運(yùn)行必須要使用1和2,2中類加載器。

  • Android中的ClassLoader的特點(diǎn)以及作用?

前面提到了委派模式。在Android中叫雙親代理模式。2者的作用與思想是一樣的 。

特點(diǎn):如果字節(jié)碼在整個(gè)加載器類樹中被一個(gè)加載器加載過(guò) 那么在整個(gè)系統(tǒng)生命周期中中都不會(huì)在重新加載 提高效率
作用:類加載的共享功能與隔離功能都是基于雙親代理模式總結(jié)而來(lái)的。
共享功能:一些底層(如Framework層)的類被頂層類加載器加載過(guò)那么以后在任何地方用到就不用再加載。
隔離功能:不同繼承路線類加載器中,加載的類都是一定是不相同的,避免用戶寫一些可見的類冒充核心的類庫(kù)。如:Object.lang.String類在程序啟動(dòng)之前就被系統(tǒng)加載了。如果我們自己寫的String會(huì)將系統(tǒng)的String類替換的話,將會(huì)出現(xiàn)嚴(yán)重的安全問(wèn)題。

雙親代理模式:
Android中的classLoader當(dāng)加載一個(gè)字節(jié)碼文件的時(shí)候首先會(huì)詢問(wèn)當(dāng)前加載器是否已經(jīng)加載過(guò)此類 如果已經(jīng)加載 那么直接返回不再重復(fù)加載。如果沒有加載,他會(huì)查詢當(dāng)前加載器的Parent類是否已經(jīng)加載過(guò)此字節(jié)碼。如果加載過(guò)直接返回Parent類加載的字節(jié)碼文件。如果(所有繼承鏈都沒有加載過(guò))那么就由子加載器加載并返回。

同一個(gè)類指的是相同的類名,包名,已經(jīng)是同一個(gè)類加載器加載的。


Android中的ClassLoader源碼講解

從上面我們知道一個(gè)應(yīng)用的運(yùn)行必須要使用BootClassLoader和PathClassLoader,2種類加載器。下面我們新建個(gè)Android項(xiàng)目來(lái)運(yùn)行下。代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ClassLoader classLoader = getClassLoader();
        Log.e("ggxiaozhi", "classLoader: "+ classLoader);
        if (classLoader.getParent()!=null){
            classLoader=classLoader.getParent();
            Log.e("ggxiaozhi", "classLoader-Parent: "+ classLoader);
        }
    }
}

打印結(jié)果:

01-12 06:25:26.880 1842-1842/? E/ggxiaozhi: classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.ggxiaozhi.hotfix-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.ggxiaozhi.hotfix-1/lib/x86, /vendor/lib, /system/lib]]]
01-12 06:25:26.880 1842-1842/? E/ggxiaozhi: classLoader-Parent: java.lang.BootClassLoader@665444a

從打印結(jié)果也可以看到確實(shí)是這樣的。下面我們進(jìn)入源碼分析下。

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded   (1)
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false); (2)
                    } 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); (3)

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

源碼也比較簡(jiǎn)單:

  1. (1)首先查找我們的ClassLoader是否加載過(guò)我們的當(dāng)前的class文件。
  2. (2)如果沒有找到就查找他的分類有沒有加載過(guò)。
  3. (3)如果父類也沒有找到過(guò)就去通過(guò)findClass(name);去加載這個(gè)類文件。

點(diǎn)進(jìn)去findClass()這個(gè)方法發(fā)現(xiàn),這是一個(gè)空實(shí)現(xiàn)說(shuō)明他的子類實(shí)現(xiàn)了這個(gè)方法。而他的子類正上上面我們提到的4種類加載器。由于我們?cè)趯?shí)際中Framework層的加載器我們接觸不到,所以重點(diǎn)分下其他三種類加載器。由于這幾個(gè)方法我們都看不到所有我們通過(guò)源碼網(wǎng)查去查詢。
源碼地址
使用教程文章

  • DexClassLoader
1/**
22 * A class loader that loads classes from {@code .jar} and {@code .apk} files
23 * containing a {@code classes.dex} entry. This can be used to execute code not
24 * installed as part of an application.
25
36 public class DexClassLoader extends BaseDexClassLoader {
55    public DexClassLoader(String dexPath, String optimizedDirectory,
56            String librarySearchPath, ClassLoader parent) {
57        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
58    }
59}

可以看到它里面就一個(gè)構(gòu)造方法,通過(guò)類的注釋可以簡(jiǎn)單理解為它是加載來(lái)自.jar和.apk本身的class文件路徑,也可以用來(lái)執(zhí)行不作為應(yīng)用程序的一部分安裝的代碼。(所以它也是我們后面要講得動(dòng)態(tài)更新加載的核心加載器)
這里面的參數(shù)含義分別為:

  • dexPath:要加載的指定文件下的dex文件路徑
  • optimizedDirectory :這是一個(gè)copy路徑??梢岳斫鈶?yīng)用在安裝時(shí),先將dex文件copy到應(yīng)用的內(nèi)部路徑,待需要加載dex文件時(shí)去應(yīng)用內(nèi)部路徑找到dex文件去加載。(中間還會(huì)做一些優(yōu)化)。這個(gè)路徑是應(yīng)用的內(nèi)部路徑,在DexClassLoader下這個(gè)參數(shù)一定不能為空。
  • librarySearchPath 加載native相關(guān)dex文件。
  • ClassLoader 父類加載器
  • PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {

37    public PathClassLoader(String dexPath, ClassLoader parent) {
38        super(dexPath, null, null, parent);
39    }
40

63    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64        super(dexPath, null, librarySearchPath, parent);
65    }
66}

從類的注解上來(lái)看這個(gè)加載器是Android使用作為它的系統(tǒng)類加載器和它的應(yīng)用程序類加載器。也就是加載Android項(xiàng)目工程中的類文件加載器。參數(shù)和上面一樣,正是因?yàn)槿鄙賝ptimizedDirectory參數(shù)所以它只能加載項(xiàng)目本省的dex文件中的類

  • BaseDexClassLoader

BaseDexClassLoader是上面2個(gè)類加載器的父類。由于上面2個(gè)類加載器都沒有具體的邏輯方法。還記上面我們?cè)诓檎褻lassLoader時(shí)知道加載類的方法是findClass(name).由于這兩個(gè)雷都沒有實(shí)現(xiàn)這個(gè)方法,那么一定就是在他們的父類BaseDexClassLoader中實(shí)現(xiàn)的。下面看下他的源碼:

29 public class BaseDexClassLoader extends ClassLoader {
30    private final DexPathList pathList;
31
45    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46            String librarySearchPath, ClassLoader parent) {
47        super(parent);
48        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
49    }
50
51    @Override
52    protected Class<?> findClass(String name) throws ClassNotFoundException {
53        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
54        Class c = pathList.findClass(name, suppressedExceptions);
55        if (c == null) {
56            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on    path: " + pathList);
57            for (Throwable t : suppressedExceptions) {
58                cnfe.addSuppressed(t);
59            }
60            throw cnfe;
61        }
62        return c;
63    }

在這個(gè)類中找到了上面我們?cè)贑lassLoader中未實(shí)現(xiàn)的findClass方法。發(fā)現(xiàn)他是調(diào)用了DexPathList類中的方法,并且這個(gè)類是在構(gòu)造方法中已經(jīng)初始化并肩參數(shù)傳入其中了。所以我們?cè)谶@個(gè)類中尋找一下DexPathList#findClass():

 final class DexPathList {
51    private static final String DEX_SUFFIX = ".dex";
52    private static final String zipSeparator = "!/";
53
54    /** class definition context */
55    private final ClassLoader definingContext;
56
57    /**
58     * List of dex/resource (class path) elements.
59     * Should be called pathElements, but the Facebook app uses reflection
60     * to modify 'dexElements' (http://b/7726934).
61     */
62    private Element[] dexElements;
63
64    /** List of native library path elements. */
65    private final Element[] nativeLibraryPathElements;
66
67    /** List of application native library directories. */
68    private final List<File> nativeLibraryDirectories;
69
70    /** List of system native library directories. */
71    private final List<File> systemNativeLibraryDirectories;
72
73    /**
74     * Exceptions thrown during creation of the dexElements list.
75     */
76    private IOException[] dexElementsSuppressedExceptions;
77
78   
96    public DexPathList(ClassLoader definingContext, String dexPath,
97            String librarySearchPath, File optimizedDirectory) {
98         ...

122        this.definingContext = definingContext;
123
124        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
125        // save dexPath for BaseDexClassLoader
126        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
127                                           suppressedExceptions, definingContext);
128
129 
140        this.systemNativeLibraryDirectories =
141                splitPaths(System.getProperty("java.library.path"), true);
142        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
143        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
144
145        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
146                                                          suppressedExceptions,
147                                                          definingContext);
148
149        if (suppressedExceptions.size() > 0) {
150            this.dexElementsSuppressedExceptions =
151                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
152        } else {
153            dexElementsSuppressedExceptions = null;
154        }
155    }
156
157   
        ...
        
         private static Element[] makePathElements(List<File> files, File optimizedDirectory,
280                                              List<IOException> suppressedExceptions) {
281        return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);
282    }
283
       //這個(gè)方法的作用就是將指定路徑class文件轉(zhuǎn)化成dexfile(dex文件) 同時(shí)存在Element[]數(shù)組中 //最后在findClass文件中使用
284    private static Element[] makeElements(List<File> files, File optimizedDirectory,
285                                          List<IOException> suppressedExceptions,
286                                          boolean ignoreDexFiles,
287                                          ClassLoader loader) {
        //創(chuàng)建Element數(shù)組
288        Element[] elements = new Element[files.size()];
289        int elementsPos = 0;
290        /*
291         * Open all files and load the (direct or contained) dex files
292         * up front.
293         */
294        for (File file : files) {//遍歷dex文件集合
295            File zip = null;
296            File dir = new File("");
            //dex文件對(duì)應(yīng)的java類
297            DexFile dex = null;
               //獲取文件路徑
298            String path = file.getPath();
               //獲取文件名
299            String name = file.getName();
300
                //path是文件夾繼續(xù)往下遍歷
301            if (path.contains(zipSeparator)) {
302                String split[] = path.split(zipSeparator, 2);
303                zip = new File(split[0]);
304                dir = new File(split[1]);
305            } else if (file.isDirectory()) {
306                // We support directories for looking up resources and native libraries.
307                // Looking up resources in directories is useful for running libcore tests.
308                elements[elementsPos++] = new Element(file, true, null, null);
309            } else if (file.isFile()) {//如果是文件 最后都會(huì)調(diào)用loadDexFile()f方法創(chuàng)建dex文件
310                if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {//這個(gè)文件是不是以.dex文件為后綴的
311                    // Raw dex file (not inside a zip/jar).
312                    try {
                           //如果是就創(chuàng)建一個(gè)dex文件
313                        dex = loadDexFile(file, optimizedDirectory, loader, elements);
314                    } catch (IOException suppressed) {
315                        System.logE("Unable to load dex file: " + file, suppressed);
316                        suppressedExceptions.add(suppressed);
317                    }
318                } else {//如果這個(gè)文件值z(mì)ip格式
319                    zip = file;
320
321                    if (!ignoreDexFiles) {
322                        try {
323                            dex = loadDexFile(file, optimizedDirectory, loader, elements);
324                        } catch (IOException suppressed) {
325                            /*
326                             * IOException might get thrown "legitimately" by the DexFile constructor if
327                             * the zip file turns out to be resource-only (that is, no classes.dex file
328                             * in it).
329                             * Let dex == null and hang on to the exception to add to the tea-leaves for
330                             * when findClass returns null.
331                             */
332                            suppressedExceptions.add(suppressed);
333                        }
334                    }
335                }
336            } else {
337                System.logW("ClassLoader referenced unknown path: " + file);
338            }
339
340            if ((zip != null) || (dex != null)) {
341                elements[elementsPos++] = new Element(dir, false, zip, dex);
342            }
343        }
344        if (elementsPos != elements.length) {
345            elements = Arrays.copyOf(elements, elementsPos);
346        }
347        return elements;
348    }

        /**
351     * Constructs a {@code DexFile} instance, as appropriate depending on whether
352     * {@code optimizedDirectory} is {@code null}. An application image file may be associated with
353     * the {@code loader} if it is not null.
354     */
355    private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
356                                       Element[] elements)
357            throws IOException {
358        if (optimizedDirectory == null) {//這個(gè)路徑是空就說(shuō)明一個(gè)dex文件沒有 我們就要?jiǎng)?chuàng)建一個(gè)dex文件
359            return new DexFile(file, loader, elements);
360        } else {//否自會(huì)通過(guò)解壓等處理最后得到DexFile
361            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
362            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
363        }
364    }
413    public Class findClass(String name, List<Throwable> suppressed) {
414        for (Element element : dexElements) {
415            DexFile dex = element.dexFile;
416
417            if (dex != null) {
418                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
419                if (clazz != null) {
420                    return clazz;
421                }
422            }
423        }
424        if (dexElementsSuppressedExceptions != null) {
425            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
426        }
427        return null;
428    }
429
             ....
448
489
490    /**
491     * Element of the dex/resource/native library path
492     */
493    /*package*/ static class Element {
494        private final File dir;
495        private final boolean isDirectory;
496        private final File zip;
497        private final DexFile dexFile;
498
499        private ClassPathURLStreamHandler urlHandler;
500        private boolean initialized;
501
502        public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
503            this.dir = dir;
504            this.isDirectory = isDirectory;
505            this.zip = zip;
506            this.dexFile = dexFile;
507        }
508
509        @Override public String toString() {
510            if (isDirectory) {
511                return "directory \"" + dir + "\"";
512            } else if (zip != null) {
513                return "zip file \"" + zip + "\"" +
514                       (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
515            } else {
516                return "dex file \"" + dexFile + "\"";
517            }
518        }
566
567       ...
590    }
591}
592

這個(gè)類比較長(zhǎng),這里簡(jiǎn)單講解下:首先定義一些常量來(lái)規(guī)定加載.dex文件格式,同時(shí)定義了Element屬性。在構(gòu)造方法中先對(duì)一些異常處理并初始化一些常量。下面只看我們上步跟蹤的方法findClass。發(fā)現(xiàn)這個(gè)方法先遍歷了Element這個(gè)數(shù)組,而這個(gè)數(shù)組是通過(guò)在構(gòu)造方法中調(diào)用makeElements()方法初始化,然后調(diào)用DexFile#loadClassBinaryName()方法,說(shuō)明這個(gè)類也不是最終加載類的地方。不過(guò)在繼續(xù)跟蹤之前我們先對(duì)Element有個(gè)理解。其實(shí)他就是DexPathList中的一個(gè)內(nèi)部類,誰(shuí)對(duì)dex文件的包裝,將路徑與最終加載的類DexFile封裝在一起,并進(jìn)行一些字符串的拼湊。接著我們?cè)谶M(jìn)入DexFile#loadClassBinaryName()方法:

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
289        return defineClass(name, loader, mCookie, this, suppressed);
290    }
291
292    private static Class defineClass(String name, ClassLoader loader, Object cookie,
293                                     DexFile dexFile, List<Throwable> suppressed) {
294        Class result = null;
295        try {
296            result = defineClassNative(name, loader, cookie, dexFile);
297        } catch (NoClassDefFoundError e) {
298            if (suppressed != null) {
299                suppressed.add(e);
300            }
301        } catch (ClassNotFoundException e) {
302            if (suppressed != null) {
303                suppressed.add(e);
304            }
305        }
306        return result;
307    }
387      private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
388                                                  DexFile dexFile)
389            throws ClassNotFoundException, NoClassDefFoundError;

這里直接看loadClassBinaryName方法他調(diào)用了defineClass方法,最后調(diào)用defineClassNative方法。defineClassNative()這個(gè)方法是native,是用C/C++ 實(shí)現(xiàn)的,往后我們就無(wú)法查看了。不過(guò)經(jīng)過(guò)前面分析,最后native方法是大概就是通過(guò)C/C++
根據(jù)指定傳入類的name去查找dex文件中對(duì)應(yīng)的class文件相關(guān)信息數(shù)據(jù),然后將dex文件的中的運(yùn)行數(shù)據(jù)區(qū)中的數(shù)據(jù)拼成一個(gè)class字節(jié)碼返回。應(yīng)用層使用。


總結(jié):
注意dex可以理解成把所有的class文件壓縮成了一個(gè)dex文件 dex對(duì)應(yīng)轉(zhuǎn)化的是jar不是class

我的理解dex文件包含各個(gè)路徑的jar文件.zip文件 不管用沒有用到 等用到了采用類加載器去根據(jù)類名去加載這個(gè)類 信息然后最后通過(guò)native層在dex文件查找返回這個(gè)類的信息并返回。(這個(gè)整個(gè)串聯(lián)的流程我也好串聯(lián)起來(lái)。待后期有更深的研究在完善這部分) 如果大家有相關(guān)的書籍推薦下。

Android中的類加載器流程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容