###一. 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 版權聲明:本文爲博主原創文章,轉載請附上博文連接!