前言
這篇文章主要是講解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的不同:

第一點(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)單介紹。

現(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è)類組成。

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)首先查找我們的ClassLoader是否加載過(guò)我們的當(dāng)前的class文件。
- (2)如果沒有找到就查找他的分類有沒有加載過(guò)。
- (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中的類加載器流程。