前言
這篇文章主要是講解Android中的ClassLoader
Dalvik VM
Dalvik是Google公司自己設計用于Android平臺的Java虛擬機。它可以支持已轉換為.dex(即Dalvik Executable)格式的Java應用程序的運行同衣,.dex格式是專為Dalvik設計的一種壓縮格式竟块,可以減少整體文件尺寸,提高I/o操作的類查找速度所以適合內存和處理器速度有限的系統(tǒng)耐齐。
關于Dalvik更多內容參考這篇文章:Dalvik概述
說的直白一點就是針對Android而生的JVM的升級浪秘。他與JVM的不同:
第一點不用解釋
第二點后面會詳細講解
第三點:JVM只存在一個前弯,DVM 可以存在多個,某一個引用程序掛掉以后不會影響的其他程序秫逝,保證程序的穩(wěn)定性恕出。
第四點:基于棧 表示方法的調用時在棧中完成的 寄存器運行更快
ART與Dalvik的不同
ART模式英文全稱為:Android runtime,谷歌Android 4.4系統(tǒng)新增的一種應用運行模式违帆,與傳統(tǒng)的Dalvik模式不同浙巫,ART模式可以實現(xiàn)更為流暢的安卓系統(tǒng)體驗,對于大家來說刷后,只要明白ART模式可讓系統(tǒng)體驗更加流暢的畴,不過只有在安卓4.4以上系統(tǒng)中采用此功能。這里只做簡單介紹尝胆。
現(xiàn)在市面的手機基本都是ART模式了丧裁。
Android中的ClassLoader
JVM的類加載器是將字節(jié)碼文件通過讀取后加載到JVM運行時數(shù)據區(qū)。而Android中的ClassLoader的作用是一樣的含衔,只不過是加載到Dalvik中煎娇。
- Android中的ClassLoader有哪些?
Android中的ClassLoader由一下4個類組成贪染。
1:加載Framework層字節(jié)碼文件
2:加載已經安裝到系統(tǒng)中APK文件中的字節(jié)碼文件(sdk中的文件)
3:加載指定目錄中的字節(jié)碼文件(如lib引入的jar中的文件等)
4:是2.3的父類
從上面我們知道一個應用的運行必須要使用1和2缓呛,2中類加載器。
- Android中的ClassLoader的特點以及作用杭隙?
前面提到了委派模式哟绊。在Android中叫雙親代理模式。2者的作用與思想是一樣的 痰憎。
特點:如果字節(jié)碼在整個加載器類樹中被一個加載器加載過 那么在整個系統(tǒng)生命周期中中都不會在重新加載 提高效率
作用:類加載的共享功能與隔離功能都是基于雙親代理模式總結而來的票髓。
共享功能:一些底層(如Framework層)的類被頂層類加載器加載過那么以后在任何地方用到就不用再加載。
隔離功能:不同繼承路線類加載器中铣耘,加載的類都是一定是不相同的洽沟,避免用戶寫一些可見的類冒充核心的類庫。如:Object.lang.String類在程序啟動之前就被系統(tǒng)加載了涡拘。如果我們自己寫的String會將系統(tǒng)的String類替換的話玲躯,將會出現(xiàn)嚴重的安全問題。
雙親代理模式:
Android中的classLoader當加載一個字節(jié)碼文件的時候首先會詢問當前加載器是否已經加載過此類 如果已經加載 那么直接返回不再重復加載鳄乏。如果沒有加載,他會查詢當前加載器的Parent類是否已經加載過此字節(jié)碼棘利。如果加載過直接返回Parent類加載的字節(jié)碼文件橱野。如果(所有繼承鏈都沒有加載過)那么就由子加載器加載并返回。
同一個類指的是相同的類名善玫,包名水援,已經是同一個類加載器加載的密强。
Android中的ClassLoader源碼講解
從上面我們知道一個應用的運行必須要使用BootClassLoader和PathClassLoader,2種類加載器蜗元。下面我們新建個Android項目來運行下或渤。代碼如下:
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);
}
}
}
打印結果:
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
從打印結果也可以看到確實是這樣的。下面我們進入源碼分析下奕扣。
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;
}
源碼也比較簡單:
- (1)首先查找我們的ClassLoader是否加載過我們的當前的class文件薪鹦。
- (2)如果沒有找到就查找他的分類有沒有加載過。
- (3)如果父類也沒有找到過就去通過findClass(name);去加載這個類文件惯豆。
點進去findClass()這個方法發(fā)現(xiàn)池磁,這是一個空實現(xiàn)說明他的子類實現(xiàn)了這個方法。而他的子類正上上面我們提到的4種類加載器楷兽。由于我們在實際中Framework層的加載器我們接觸不到地熄,所以重點分下其他三種類加載器。由于這幾個方法我們都看不到所有我們通過源碼網查去查詢芯杀。
源碼地址
使用教程文章
-
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}
可以看到它里面就一個構造方法端考,通過類的注釋可以簡單理解為它是加載來自.jar和.apk本身的class文件路徑,也可以用來執(zhí)行不作為應用程序的一部分安裝的代碼揭厚。(所以它也是我們后面要講得動態(tài)更新加載的核心加載器)
這里面的參數(shù)含義分別為:
- dexPath:要加載的指定文件下的dex文件路徑
- optimizedDirectory :這是一個copy路徑跛梗。可以理解應用在安裝時棋弥,先將dex文件copy到應用的內部路徑核偿,待需要加載dex文件時去應用內部路徑找到dex文件去加載。(中間還會做一些優(yōu)化)顽染。這個路徑是應用的內部路徑漾岳,在DexClassLoader下這個參數(shù)一定不能為空。
- librarySearchPath 加載native相關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}
從類的注解上來看這個加載器是Android使用作為它的系統(tǒng)類加載器和它的應用程序類加載器尼荆。也就是加載Android項目工程中的類文件加載器。參數(shù)和上面一樣唧垦,正是因為缺少optimizedDirectory參數(shù)所以它只能加載項目本省的dex文件中的類
-
BaseDexClassLoader
BaseDexClassLoader是上面2個類加載器的父類捅儒。由于上面2個類加載器都沒有具體的邏輯方法。還記上面我們在查找ClassLoader時知道加載類的方法是findClass(name).由于這兩個雷都沒有實現(xiàn)這個方法振亮,那么一定就是在他們的父類BaseDexClassLoader中實現(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 }
在這個類中找到了上面我們在ClassLoader中未實現(xiàn)的findClass方法。發(fā)現(xiàn)他是調用了DexPathList類中的方法坊秸,并且這個類是在構造方法中已經初始化并肩參數(shù)傳入其中了麸祷。所以我們在這個類中尋找一下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
//這個方法的作用就是將指定路徑class文件轉化成dexfile(dex文件) 同時存在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文件對應的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()) {//如果是文件 最后都會調用loadDexFile()f方法創(chuàng)建dex文件
310 if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {//這個文件是不是以.dex文件為后綴的
311 // Raw dex file (not inside a zip/jar).
312 try {
//如果是就創(chuàng)建一個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 {//如果這個文件值zip格式
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) {//這個路徑是空就說明一個dex文件沒有 我們就要創(chuàng)建一個dex文件
359 return new DexFile(file, loader, elements);
360 } else {//否自會通過解壓等處理最后得到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
這個類比較長,這里簡單講解下:首先定義一些常量來規(guī)定加載.dex文件格式褒搔,同時定義了Element屬性阶牍。在構造方法中先對一些異常處理并初始化一些常量喷面。下面只看我們上步跟蹤的方法findClass。發(fā)現(xiàn)這個方法先遍歷了Element這個數(shù)組走孽,而這個數(shù)組是通過在構造方法中調用makeElements()方法初始化,然后調用DexFile#loadClassBinaryName()方法惧辈,說明這個類也不是最終加載類的地方。不過在繼續(xù)跟蹤之前我們先對Element有個理解磕瓷。其實他就是DexPathList中的一個內部類盒齿,誰對dex文件的包裝,將路徑與最終加載的類DexFile封裝在一起生宛,并進行一些字符串的拼湊县昂。接著我們在進入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方法他調用了defineClass方法,最后調用defineClassNative方法陷舅。defineClassNative()這個方法是native倒彰,是用C/C++ 實現(xiàn)的,往后我們就無法查看了莱睁。不過經過前面分析待讳,最后native方法是大概就是通過C/C++
根據指定傳入類的name去查找dex文件中對應的class文件相關信息數(shù)據,然后將dex文件的中的運行數(shù)據區(qū)中的數(shù)據拼成一個class字節(jié)碼返回仰剿。應用層使用创淡。
總結:
注意dex可以理解成把所有的class文件壓縮成了一個dex文件 dex對應轉化的是jar不是class
我的理解dex文件包含各個路徑的jar文件.zip文件 不管用沒有用到 等用到了采用類加載器去根據類名去加載這個類 信息然后最后通過native層在dex文件查找返回這個類的信息并返回。(這個整個串聯(lián)的流程我也好串聯(lián)起來南吮。待后期有更深的研究在完善這部分) 如果大家有相關的書籍推薦下琳彩。
Android中的類加載器流程。