Android有兩種虛擬機(jī)坟瓢,分別是Dalvik和ART。而Java有自己的虛擬機(jī)寇蚊,是大家熟知的JVM。Dalvik和ART不是標(biāo)準(zhǔn)的JVM棍好,在類加載機(jī)制上仗岸,Android和Java是有區(qū)別的。(復(fù)習(xí)擴(kuò)展可點http://www.reibang.com/p/e3abb3556e7e)
我們的apk要在設(shè)備上跑起來借笙,首先需要將對應(yīng)的類加載到設(shè)備內(nèi)存中扒怖。那Android中是怎么實現(xiàn)的呢?
首先在Android中业稼,ClassLoader是專門用來處理類加載工作的盗痒,被稱作類加載器。我們?nèi)タ丛创a盼忌,會發(fā)現(xiàn)ClassLoader是一個抽象類积糯。實際開發(fā)過程中,我們一般是使用其具體的子類DexClassLoader谦纱、PathClassLoader這些類加載器來加載類的看成。它們的不同之處是:
DexClassLoader可以加載jar、apk及dex文件跨嘉,可以從SD卡中加載未安裝的apk川慌,并且會在指定的outpath路徑釋放出dex文件。
PathClassLoader:不能主動從zip包中釋放出dex祠乃,所以只支持直接操作dex格式文件梦重,或者已經(jīng)安裝的apk。
多說兩句亮瓷。已經(jīng)安裝的apk會在設(shè)備data/dalvik目錄中緩存的dex文件琴拧。怎么查看呢?設(shè)備用USB連上電腦嘱支,在AS中打開Android Device Monitor(不懂的自己百度)蚓胸,找到data/dalvik目錄,就可以看到PathClassLoader加載的就是該目錄下的dex文件除师。如果你的設(shè)備是已經(jīng)Root過的沛膳,那直接可以在設(shè)備文件目錄下查看,否則只能通過Device Monitor查看汛聚。
這二者的不同特點在Android系統(tǒng)源碼中也有說明锹安,大家可以看系統(tǒng)源碼注釋說明。英文我就不翻譯了倚舀。
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
*/
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
DexClassLoader的構(gòu)造函數(shù)參數(shù)叹哭。
第一個參數(shù):dex壓縮文件的路徑。
第二個參數(shù):將jar瞄桨、apk文件解壓出的dex文件存放的目錄话速。
第三個參數(shù):是C/C++依賴的本地庫文件目錄,可以為null芯侥。
第四個參數(shù):上一級的類加載器泊交。在Android中以context.getClassLoader()作為父裝載器。
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
我們已經(jīng)說過了柱查,PathClassLoader只能直接操作dex文件廓俭,所以當(dāng)我們看到PathClassLoader構(gòu)造函數(shù)第二個參數(shù)直接為null就很明白,PathClassLoader不像DexClassLoader 需要解壓出dex文件唉工,而是直接操作研乒,就不用專門再將指定的outpath路徑釋放出dex文件。
還有一點需要強(qiáng)調(diào):optimizedDirectory必須是一個內(nèi)部存儲路徑淋硝,加載的可執(zhí)行文件雹熬,即dex文件宽菜,一定要存放在內(nèi)部存儲。DexClassLoader可以指定自己的optimizedDirectory竿报,所以它可以加載外部的dex铅乡,因為這個dex會被復(fù)制到內(nèi)部路徑的optimizedDirectory;而PathClassLoader沒有optimizedDirectory烈菌,所以它只能加載內(nèi)部的dex阵幸,這些大都是存在系統(tǒng)中已經(jīng)安裝過的apk里面的。
眼尖的朋友應(yīng)該早發(fā)現(xiàn)芽世,DexClassLoader 和PathClassLoader 的父類是BaseDexClassLoader挚赊,并不是ClassLoader。對的济瓢。不過BaseDexClassLoader也是繼承自ClassLoader荠割。DexClassLoader 和PathClassLoader 兩者只是簡單的對BaseDexClassLoader做了一下封裝,具體的實現(xiàn)還是在父類里旺矾。我們先看BaseDexClassLoader的構(gòu)造函數(shù)涨共。
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
BaseDexClassLoader的構(gòu)造函數(shù)做了兩件事:1、super宠漩,2举反、構(gòu)造了一個 DexPathList 實例保存在 pathList 中。點擊DexPathList 進(jìn)去看看扒吁。
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
...//省去一些判空等源碼
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
}
DexPathList 構(gòu)造函數(shù)的第二個參數(shù)指的是優(yōu)化后的 dex 存放目錄火鼻。實際上,dex 其實并不能被虛擬機(jī)直接加載雕崩,它需要系統(tǒng)的優(yōu)化工具優(yōu)化后才能真正被利用魁索。優(yōu)化之后的 dex 文件我們把它叫做 odex (optimized dex,說明這是被優(yōu)化后的 dex)文件盼铁。其實從 class 到 dex 也算是經(jīng)歷了一次優(yōu)化粗蔚,這種優(yōu)化的是機(jī)器無關(guān)的優(yōu)化,也就是說不管將來運(yùn)行在什么機(jī)器上饶火,這種優(yōu)化都是遵循固定模式的鹏控,因此這種優(yōu)化發(fā)生在 apk 編譯。而從 dex 文件到 odex 文件肤寝,是機(jī)器相關(guān)的優(yōu)化当辐,它使得 odex 適配于特定的硬件環(huán)境,不同機(jī)器這一步的優(yōu)化可能有所不同鲤看,所以這一步需要在應(yīng)用安裝等運(yùn)行時期由機(jī)器來完成缘揪。
總而言之,BaseDexClassLoader中的pathList中包含一個DexFile的數(shù)組dexElements,dexPath傳入的原始dex(.apk找筝、.zip蹈垢、.jar等)文件在optimizedDirectory文件夾中生成相應(yīng)的優(yōu)化后的odex文件,dexElements數(shù)組就是這些odex文件的集合袖裕,如果不分包一般這個數(shù)組只有一個Element元素耘婚,也就只有一個DexFile文件。
加載類的過程
Android中陆赋,ClassLoader用loadClass方法來加載我們需要的類。例如:
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";
String dexOutputDirs = Environment.getExternalStorageDirectory().toString();
DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());
Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
那我們來看看ClassLoader類的這個loadClass方法嚷闭。
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;
}
從源碼中我們也可以看出攒岛,loadClass方法在加載一個類的實例的時候,會先查詢當(dāng)前ClassLoader實例是否加載過此類胞锰,有就返回灾锯;
如果沒有。查詢Parent是否已經(jīng)加載過此類嗅榕,如果已經(jīng)加載過顺饮,就直接返回Parent加載的類;
如果繼承路線上的ClassLoader都沒有加載凌那,才由Child執(zhí)行類的加載工作兼雄;
這種現(xiàn)象被稱作:雙親代理模型。
這樣做有個明顯的特點帽蝶,如果一個類被位于樹根的ClassLoader加載過赦肋,那么在以后整個系統(tǒng)的生命周期內(nèi),這個類永遠(yuǎn)不會被重新加載励稳。
loadClass方法調(diào)用了findClass方法佃乘,而BaseDexClassLoader重載了這個方法,得到BaseDexClassLoader看看驹尼。
@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;
}
結(jié)果還是調(diào)用了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;
}
發(fā)現(xiàn)又調(diào)用了DexFile 的loadClassBinaryName方法。
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
throws ClassNotFoundException, NoClassDefFoundError;
loadClassBinaryName最終是調(diào)用了native defineClassNative方法新翎。到此程帕,Android的加載過程我們終于看完了。
如果大家看不到DexClassLoader 和PathClassLoader 等源碼地啰,那你需要下載Android系統(tǒng)源碼骆捧,或者http://androidxref.com/在線選擇Android系統(tǒng)版本,查看源碼髓绽。
參考:http://blog.csdn.net/jiangwei0910410003/article/details/17679823