private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
209 /*
210 * Open all files and load the (direct or contained) dex files
211 * up front.
212 */
213 for (File file : files) {//遍歷所有的dex文件
214 File zip = null;
215 DexFile dex = null;//這是核心的類(lèi),處理將dex文件轉(zhuǎn)化成對(duì)應(yīng)的DexFile對(duì)象
216 String name = file.getName();
217
218 if (name.endsWith(DEX_SUFFIX)) {//.dex結(jié)尾(針對(duì)PathClassLoader處理)
219 // Raw dex file (not inside a zip/jar).
220 try {
221 dex = loadDexFile(file, optimizedDirectory);//核心方法,內(nèi)部是調(diào)用的DexFile的loadDex()方法
222 } catch (IOException ex) {
223 System.logE("Unable to load dex file: " + file, ex);
224 }
225 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
226 || name.endsWith(ZIP_SUFFIX)) {//.dex .jar .apk 結(jié)尾 (針對(duì)DexClassLoader處理)
227 zip = file;
228
229 try {
230 dex = loadDexFile(file, optimizedDirectory);
231 } catch (IOException suppressed) {
232 /*
233 * IOException might get thrown "legitimately" by the DexFile constructor if the
234 * zip file turns out to be resource-only (that is, no classes.dex file in it).
235 * Let dex == null and hang on to the exception to add to the tea-leaves for
236 * when findClass returns null.
237 */
238 suppressedExceptions.add(suppressed);
239 }
240 } else if (file.isDirectory()) {
241 // We support directories for looking up resources.
242 // This is only useful for running libcore tests.
243 elements.add(new Element(file, true, null, null));//創(chuàng)建Element對(duì)象
244 } else {
245 System.logW("Unknown file type for: " + file);
246 }
247
248 if ((zip != null) || (dex != null)) {
249 elements.add(new Element(file, false, zip, dex));
250 }
251 }
252
253 return elements.toArray(new Element[elements.size()]);
254 }
3.Context的getClassLoader()為何返回是PathClassLoader,探索其中奧秘
我們都知道getApplicationContext()的返回的實(shí)現(xiàn)類(lèi)是ContextImpl,下面來(lái)看看ContextImpl的getClassLoader()的代碼實(shí)現(xiàn):
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
我們看到如果mPackageInfo不為null,就調(diào)用它的getClassLoader()方法,否則調(diào)用ClassLoader.getSystemClassLoader(),這里我們看到了ClassLoader的getSystemClassLoader()方法,但是這里還不是重點(diǎn),重點(diǎn)是mPackageInfo這個(gè)對(duì)象,這是甚么呢,它是1個(gè)LoadedAPK對(duì)象。它又是甚么呢?
官方文檔說(shuō)明以下:
Local state maintained about a currently loaded .apk.
LoaderAPK對(duì)象是apk在內(nèi)存中的表示。通過(guò)這個(gè)LoaderApk對(duì)象可以拿到apk中代碼和資源,乃至里面Activity和Service等信息。
那末它又是哪里創(chuàng)建的,又是甚么時(shí)候創(chuàng)建的呢,如果你了解Activity的啟動(dòng)進(jìn)程,你就明白了(如果想了解,推薦老羅的文章),這里就不贅述了。在ActivityThread里面有1個(gè)mPackages的map類(lèi)型的成員變量,根據(jù)鍵值(packageName)存儲(chǔ)對(duì)應(yīng)的LoadedApk對(duì)象。啟動(dòng)Activity的時(shí)候要調(diào)用LoadedApk的getClassLoader(),來(lái)加載對(duì)應(yīng)的Activity class文件,然后通過(guò)反射創(chuàng)建這個(gè)activity的實(shí)例;那末獲得這個(gè)對(duì)象,會(huì)先去mPackages中去查找有無(wú)緩存,如果沒(méi)有就創(chuàng)建1個(gè)新的LoadedAPK對(duì)象。
下面代碼截取自ActivityThread中啟動(dòng)Activity的進(jìn)程中創(chuàng)建LoadedApk的代碼:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 獲得userid信息
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
// 嘗試獲得緩存信息
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 緩存沒(méi)有命中,直接new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 省略。。更新緩存
return packageInfo;
}
}
下面看上面使用到的LoadedApk的構(gòu)造函數(shù),其實(shí)LoadedApk還有1個(gè)構(gòu)造函數(shù),在ContextImpl創(chuàng)建自己的實(shí)例的同時(shí)創(chuàng)建其LoadedApk的成員變量的時(shí)候使用了。
108 public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
109 CompatibilityInfo compatInfo,
110 ActivityThread mainThread, ClassLoader baseLoader,
111 boolean securityViolation, boolean includeCode) {
<span style="white-space:pre"> </span> .......(省略)
126
127 if (mAppDir == null) {
128 if (ActivityThread.mSystemContext == null) {//這個(gè)context很重要,1個(gè)ActivityThread只有這1個(gè),是靜態(tài)全局的
129 ActivityThread.mSystemContext =
130 ContextImpl.createSystemContext(mainThread);
131 ActivityThread.mSystemContext.getResources().updateConfiguration(
132 mainThread.getConfiguration(),
133 mainThread.getDisplayMetricsLocked(compatInfo, false),
134 compatInfo);
135 //Slog.i(TAG, "Created system resources "
136 // + mSystemContext.getResources() + ": "
137 // + mSystemContext.getResources().getConfiguration());
138 }
139 mClassLoader = ActivityThread.mSystemContext.getClassLoader();//這個(gè)ClassLoader就是最后返回的那個(gè)ClassLoader
140 mResources = ActivityThread.mSystemContext.getResources();
141 }
142 }
143
看到這里,我們只要終究這個(gè)CLassLoader的來(lái)源是從Context那里后去的,也就是ActivityThread的mSystemContext里面的ClassLoader,我們來(lái)看1下這個(gè)mSystemContext的創(chuàng)建進(jìn)程,代碼以下:
1458 static ContextImpl createSystemContext(ActivityThread mainThread) {
1459 ContextImpl context = new ContextImpl();
1460 context.init(Resources.getSystem(), mainThread);//這個(gè)init操作也沒(méi)有創(chuàng)建他里面LoadedApk成員變量
1461 return context;
1462 }
所以終究調(diào)用的代碼,就是最開(kāi)始的ContextImpl的getClassLoader()方法,并且mPackageInfo(LoadedAPK對(duì)象)為null,所以終究調(diào)用的是ClassLoader.getSystemClassLoader(),所以終究結(jié)論就是系統(tǒng)ClassLoader是通過(guò)ClassLoader.getSystemClassLoader()創(chuàng)建。
4.揭開(kāi)ClassLoader.getSystemClassLoader()在Android中的神秘面紗
其代碼以下:
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
對(duì)最有1個(gè)核心方法createSystemClassLoader(),官方說(shuō)明以下:
Create the system class loader. Note this is NOT the bootstrap class
loader (which is managed by the VM). We use a null value for the parent
to indicate that the bootstrap loader is our parent.
創(chuàng)建系統(tǒng)的ClassLoader。注釋:這不是bootstrap ClassLoader(被虛擬機(jī)管理的ClassLoader)。我們使用null作為我們系統(tǒng)ClassLoader的parent來(lái)表明bootstrap就是我們的系統(tǒng)ClassLoader的parent。這里也就是充分辯明了Android系統(tǒng)不是使用的java原生的bootstrap來(lái)加載,而是使用自己的創(chuàng)建的套機(jī)制。取代bootstrap的是用null(bootstrap不是1個(gè)ClassLoader對(duì)象,所以他的子級(jí)ClassLoader調(diào)用getParent()返回的是null),而取代java中第2級(jí)的ClassLoader使用Android中創(chuàng)建的最基層的BootClassLoader(也就是上面的PathClassLoader的parent)。
這個(gè)BootClassLoader是單例的,所以全局只有1個(gè),我們也能夠得出,系統(tǒng)所有履行裝載類(lèi)的操作,都會(huì)履行到這個(gè)對(duì)象。代碼以下:
public BootClassLoader() {
super(null, true);//他的parent為null(摹擬它的parent是bootstrap)
}
還有我們看到上面PathClassLoader構(gòu)造函數(shù)中傳遞的第1個(gè)參數(shù)classPath,這個(gè)就是我們之前打印出來(lái)的dexPath。為何系統(tǒng)的ClassLoader.getSystemClassLoader()返回的PathClassLoader 的dexPath指向的是當(dāng)前apk的安裝路徑;但是我們自己創(chuàng)建的返回的PathClassLoader的dexPath卻是. 我們發(fā)現(xiàn)是由上面的System.getProperty("java.class.path",".");決定的,看到get操作,相對(duì)的也就會(huì)有set操作,那末這個(gè)值是甚么時(shí)候設(shè)置的呢。這我們就要追溯到Android
framwork中System中的initSystemProperty()方法中,調(diào)用VMRuntime.getRuntime().classPath()的值,這個(gè)值應(yīng)當(dāng)就是當(dāng)前apk的安裝路徑。
代碼以下: 路徑:/libcore/luni/src/main/java/java/lang/System.java(4.0.4)
private static void initSystemProperties() {
VMRuntime runtime = VMRuntime.getRuntime();
Properties p = new Properties();
String projectUrl = "http://www.android.com/";
String projectName = "The Android Project";
p.put("java.boot.class.path", runtime.bootClassPath());
p.put("java.class.path", runtime.classPath());
}
但是,為何我們自己調(diào)用,獲得這個(gè)值就是. 我的料想就是 系統(tǒng)創(chuàng)建完對(duì)應(yīng)的ClassLoader以后,就將這個(gè)值修改成了.(固然這僅僅是我個(gè)人的料想,希望了解的大神能幫我解答1下,由于我實(shí)在是找不到緣由,困惑好久了)。
5.總結(jié)
1.Android系統(tǒng)最頂級(jí)的ClassLoader是BootClassLoader(替換java中第2級(jí)的ext ClassLoader),而用來(lái)加載系統(tǒng)的類(lèi)是使用的以這兒BootClassLoader為parent的PathClassLoader(替換java中第3級(jí)加載classpath的ClassLoader)。
2.Android系統(tǒng)的PathClassLoader的dexPath(要加載類(lèi)的路徑),指向當(dāng)前apk安裝的路徑,然后使用DexFile來(lái)解析對(duì)應(yīng)的dex文件,裝載里面的class
3.DexClassLoader,主要用于加載外部插件,也就是可以直接加載1個(gè)apk文件,現(xiàn)在的插件化動(dòng)態(tài)加載機(jī)制,熱修復(fù)等都要使用到它的特性,固然直接使用里面的DexFile直接加載dex文件也是可以(AndFix就是這樣做的)。
4.自己遇到的坑:我之前在學(xué)習(xí)動(dòng)態(tài)化的時(shí)候,看完原理以后,自己創(chuàng)建1個(gè)DexClassLoader ,它的parent我設(shè)置的是系統(tǒng)PathClassLoader(也就是context.getClassLoader()),但是load外部apk中的類(lèi)的時(shí)候,報(bào)錯(cuò):當(dāng)前apk的安裝路徑下(也就是這個(gè)apk文件對(duì)應(yīng)生成的ODEX文件)找不到對(duì)應(yīng)的類(lèi),也就是ClassNotFoundException,但是自己不明白為何我用DexClassLoader加載的外部apk,但是它卻到系統(tǒng)中去尋覓。現(xiàn)在明白了由于我傳遞的是PathClassLoader的dexPath是指向系統(tǒng)的apk的路徑的,自然會(huì)到那里去找。但是如果傳遞的是ClassLoader.getSystemClassLoader(),由于dexPath是.
,是Directory,就不會(huì)創(chuàng)建DexFile,也就不會(huì)走DexFile的loadDexFile()方法,而是直接調(diào)用它的parent也就是BootClassLoader的findClass()來(lái)裝載當(dāng)前類(lèi)。
文章終究寫(xiě)完了,鄙人愚鈍,水平有限,文章難免有錯(cuò),歡迎指出。