類加載器

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查看汛聚。

image.png

這二者的不同特點在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)版本,查看源碼髓绽。

image.png
image.png

參考:http://blog.csdn.net/jiangwei0910410003/article/details/17679823

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敛苇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枫攀,老刑警劉巖括饶,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異来涨,居然都是意外死亡图焰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蹦掐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來技羔,“玉大人,你說我怎么就攤上這事卧抗√倮模” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵社裆,是天一觀的道長拙绊。 經(jīng)常有香客問我,道長泳秀,這世上最難降的妖魔是什么标沪? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮嗜傅,結(jié)果婚禮上金句,老公的妹妹穿的比我還像新娘。我一直安慰自己吕嘀,他們只是感情好趴梢,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著币他,像睡著了一般坞靶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝴悉,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天彰阴,我揣著相機(jī)與錄音,去河邊找鬼拍冠。 笑死尿这,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庆杜。 我是一名探鬼主播射众,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晃财!你這毒婦竟也來了叨橱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罗洗,沒想到半個月后愉舔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伙菜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年轩缤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贩绕。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡火的,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出淑倾,到底是詐尸還是另有隱情馏鹤,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布踊淳,位于F島的核電站,受9級特大地震影響陕靠,放射性物質(zhì)發(fā)生泄漏迂尝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一剪芥、第九天 我趴在偏房一處隱蔽的房頂上張望垄开。 院中可真熱鬧,春花似錦税肪、人聲如沸溉躲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锻梳。三九已至,卻和暖如春净捅,著一層夾襖步出監(jiān)牢的瞬間疑枯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工蛔六, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留荆永,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓国章,卻偏偏與公主長得像具钥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子液兽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容