【Android熱修復與插件化 三】ClassLoader詳解

###一. Android中ClassLoader的種類
Android的ClassLoader與Java的ClassLoader基本是一一對應的。若是對Java的ClassLoader不是很瞭解,能夠參考《【Java 虛擬機】類加載器》。java

BootClassLoader(Java的BootStrap ClassLoader)
用於加載Android Framework層class文件。
PathClassLoader(Java的App ClassLoader)
用於加載已經安裝到系統中的apk中的class文件。
DexClassLoader(Java的Custom ClassLoader)
用於加載指定目錄中的class文件。
BaseDexClassLoader
是PathClassLoader和DexClassLoader的父類。
###二. Android中ClassLoader的特色
遵循雙親委派模型
ClassLoader在加載一個class文件時:會詢問當前ClassLoader是否已經加載過子類,若是已經加載過則直接返回,再也不重複加載。若是沒有加載過,會去查詢當前ClassLoader的parent是否已經加載過。android

由於遵循雙親委派模型,Android中的classLoader具備兩個特色:c++

類加載共享
當一個class文件被任何一個ClassLoader加載過,就不會再被其餘ClassLoader加載。
類加載隔離
不一樣ClassLoader加載的class文件確定不是一個。舉個栗子,一些系統層級的class文件在系統初始化的時候被加載,好比java.net.String,這個是在應用啓動前就被系統加載好的。若是在一個應用裏能簡單地用一個自定義的String類把這個String類替換掉的話,將有嚴重的安全問題。
###三. ClassLoader源碼詳解
咱們從ClassLoader.java的loadClass()方法看起。咱們知道Android的ClassLoader是實現了雙親委派模型的,咱們來從源碼角度來看下是如何實現的。
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) {
                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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
能夠看到,先是判斷ClassLoader自身是否加載過該class文件,若是沒有再判斷父ClassLoader是否加載過,若是都沒有加載過再本身去加載。這和咱們上述的雙親委派模型思想徹底一致。
好了,咱們來看下ClassLoader是如何去加載class文件的呢?也就是去看下findClass()方法的具體實現。app

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
1
2
3
很遺憾,findClass()方法是一個空實現,也就是說它的具體實現是交給子類的。ide

如圖,能夠看到DexClassLoader和PathClassLoader是ClassLoader的間接實現類。
因此,下面咱們來着重講解一下DexClassLoader和PathClassLoader的源碼。
###四. DexClassLoader、PathClassLoader、BaseDexClassLoader源碼詳解ui

public class DexClassLoader extends BaseDexClassLoader {this

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
1
2
3
4
5
6
7
能夠看到DexClassLoader的源碼很是簡單,只有一個構造方法。咱們來看下其四個參數都是什麼含義。url

dexPath。要加載的dex文件路徑。
optimizedDirectory。dex文件要被copy到的目錄路徑。
libraryPath。apk文件中類要使用的c/c++代碼。
parent。父裝載器,也就是真正loadclass的裝載器。
咱們接下來看PathClassLoader的源碼。

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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
它的源碼也只是有兩個構造方法,咱們來看第二個構造方法,能夠看出,它與DexClassLoader的構造方法的區別就是少了一個要把dex文件copy到的目錄路徑。正是由於缺乏這個路徑,咱們的PathClassLoader只能用來加載安裝過的apk中的dex文件。
這兩個ClassLoader的真正核心方法都在BaseDexClassLoader中,咱們如今來看下源碼。

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, 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;
    }
    /**
     * @hide
     */
    public void addDexPath(String dexPath) {
        pathList.addDexPath(dexPath, null /*optimizedDirectory*/);
    }

    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }

    @Override
    protected Enumeration<URL> findResources(String name) {
        return pathList.findResources(name);
    }

    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }


    protected synchronized Package getPackage(String name) {
        if (name != null && !name.isEmpty()) {
            Package pack = super.getPackage(name);

            if (pack == null) {
                pack = definePackage(name, "Unknown", "0.0", "Unknown",
                        "Unknown", "0.0", "Unknown", null);
            }

            return pack;
        }

        return null;
    }

    /**
     * @hide
     */
    public String getLdLibraryPath() {
        StringBuilder result = new StringBuilder();
        for (File directory : pathList.getNativeLibraryDirectories()) {
            if (result.length() > 0) {
                result.append(':');
            }
            result.append(directory);
        }

        return result.toString();
    }

    @Override public String toString() {
        return getClass().getName() + "[" + pathList + "]";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
能夠看到,這個類的實現也是比較簡單。咱們來分析一下。
咱們看到BaseDexClassLoader有一個成員變量DexPathList,其次它的核心方法是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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
咱們看到,findClass實際上是經過成員變量pathList的findClass()方法來查找的。
因此,咱們接下來還須要去看DexPathList的源碼。
###五. DexPathList源碼詳解(重點)
因爲DexPathList源碼較長,咱們這裏分段講解。
####1. 構造方法

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, 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>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);

        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
構造方法裏最重要的一行代碼是

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);
1
2
這行代碼的意思是構造一個Element數組。那麼Element是什麼呢?它是DexPathList的一個內部類。

static class Element {
        private final File dir;
        private final boolean isDirectory;
        private final File zip;
        private final DexFile dexFile;

        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

        public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
            this.dir = dir;
            this.isDirectory = isDirectory;
            this.zip = zip;
            this.dexFile = dexFile;
        }

        @Override public String toString() {
            if (isDirectory) {
                return "directory \"" + dir + "\"";
            } else if (zip != null) {
                return "zip file \"" + zip + "\"" +
                       (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
            } else {
                return "dex file \"" + dexFile + "\"";
            }
        }

        public synchronized void maybeInit() {
            if (initialized) {
                return;
            }

            initialized = true;

            if (isDirectory || zip == null) {
                return;
            }

            try {
                urlHandler = new ClassPathURLStreamHandler(zip.getPath());
            } catch (IOException ioe) {
                /*
                 * Note: ZipException (a subclass of IOException)
                 * might get thrown by the ZipFile constructor
                 * (e.g. if the file isn't actually a zip/jar
                 * file).
                 */
                System.logE("Unable to open zip file: " + zip, ioe);
                urlHandler = null;
            }
        }

        public String findNativeLibrary(String name) {
            maybeInit();

            if (isDirectory) {
                String path = new File(dir, name).getPath();
                if (IoUtils.canOpenReadOnly(path)) {
                    return path;
                }
            } else if (urlHandler != null) {
                // Having a urlHandler means the element has a zip file.
                // In this case Android supports loading the library iff
                // it is stored in the zip uncompressed.

                String entryName = new File(dir, name).getPath();
                if (urlHandler.isEntryStored(entryName)) {
                  return zip.getPath() + zipSeparator + entryName;
                }
            }

            return null;
        }

        public URL findResource(String name) {
            maybeInit();

            // We support directories so we can run tests and/or legacy code
            // that uses Class.getResource.
            if (isDirectory) {
                File resourceFile = new File(dir, name);
                if (resourceFile.exists()) {
                    try {
                        return resourceFile.toURI().toURL();
                    } catch (MalformedURLException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }

            if (urlHandler == null) {
                /* This element has no zip/jar file.
                 */
                return null;
            }
            return urlHandler.getEntryUrlOrNull(name);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
其中,它有一個很是重要的成員變量DexFile。
接着回到主流程,咱們經過makeDexElements()方法獲得了一個elements數組。那麼,makeDexElements()方法具體幹了什麼呢?

private static Element[] makeElements(List<File> files, File optimizedDirectory,
                                          List<IOException> suppressedExceptions,
                                          boolean ignoreDexFiles,
                                          ClassLoader loader) {
        Element[] elements = new Element[files.size()];
        int elementsPos = 0;
        /*
         * 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[elementsPos++] = new Element(file, true, null, null);
            } else if (file.isFile()) {
                if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory, loader, elements);
                    } catch (IOException suppressed) {
                        System.logE("Unable to load dex file: " + file, suppressed);
                        suppressedExceptions.add(suppressed);
                    }
                } else {
                    zip = file;

                    if (!ignoreDexFiles) {
                        try {
                            dex = loadDexFile(file, optimizedDirectory, loader, elements);
                        } catch (IOException suppressed) {
                            suppressedExceptions.add(suppressed);
                        }
                    }
                }
            } else {
                System.logW("ClassLoader referenced unknown path: " + file);
            }

            if ((zip != null) || (dex != null)) {
                elements[elementsPos++] = new Element(dir, false, zip, dex);
            }
        }
        if (elementsPos != elements.length) {
            elements = Arrays.copyOf(elements, elementsPos);
        }
        return elements;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
它的做用是經過loadDexFile()方法把dex文件都加載出來,而後返回一個elements數組。
接着,咱們來看DexPathList中最重要的方法findClass()。

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;     } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 它就是經過遍歷elements數組,拿到裏面的每個dex文件,經過DexFile的loadClassBinaryName()方法找到class字節碼。經過查看DexFile源碼,能夠得知loadClassBinaryName()方法最終是調用的底層C++方法來load class。 至此,咱們就完整地瞭解了ClassLoader加載class的具體實現。 ###六. 總結 本文講解了Android中幾種ClassLoader的做用,而且從源碼角度講解了class的加載過程。在Android中加載class,其實最終是經過DexPathList的findClass來加載的。 另外,這裏貼一下幾個文件源碼的查看地址。 http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java 以上就是本文的所有內容,若有疑問,歡迎留言提問。 ---------------------  做者:Colin_Mindset  來源:CSDN  原文:https://blog.csdn.net/colinandroid/article/details/80712045  版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索