PathClassLoader棠众,DexClassLoader,mutidex琳疏,熱修復(fù)有决,art 闸拿,dalvik總結(jié)

轉(zhuǎn)載請注明出處:
PathClassLoader和DexClassLoader區(qū)別和各自在mutidex,熱更新等的使用

地址:http://www.reibang.com/p/54378566aa86

目錄

一直對一些概念书幕,知道的一知半解新荤。也看過記住過,不過過后又忘了台汇。這里做一些總結(jié)苛骨±橄梗可以分享,也可以自己以后查閱痒芝。源碼什么的其實都不是很難俐筋,之前分析過mutidex的源碼,也懶得貼了严衬。這個博客只是對一些概念的總結(jié)澄者。源碼網(wǎng)上其實很多,可以如果需要请琳,可以自己找找哈~粱挡。最后我也貼了一些。

Dalvik

當(dāng)初設(shè)計Dalvik的時候俄精,設(shè)定的是 安裝app的時候只會把主dex封裝成element放到PathclassLoader里類型是DexPathList的成員變量pathList中(即new PathclassLoader時询筏,只傳入了主dex的路徑,根據(jù)這個路徑生成pathList竖慧。關(guān)于android中的ClassLoader等細(xì)節(jié)知識看下面注解1)嫌套。DexPathList是什么呢?它內(nèi)部含有DexFile數(shù)組圾旨。DexFile可以理解就是dex文件的封裝灌危,DexFile中持有dex的路徑,以及一些方法比如loadClass()碳胳,其在dex真正加載到內(nèi)存時調(diào)用勇蝙;openDexFile()其在new DexFile()時調(diào)用,用于檢查dex文件是不是正確挨约,以及進(jìn)行dex->odex的優(yōu)化工作味混。

在app啟動后,application#attachBaseContext()時诫惭,mutidex會檢查是不是之前已經(jīng)有了緩存的從dex 文件翁锡,如果沒有,那么就從apk中解壓出來那些從dex(耗時)文件夕土。如果有馆衔,那么就使用這些緩存的從dex文件。注意緩存的是dex文件怨绣,不是DexFile角溃。所以每次冷啟動app時,不管用的是緩存的dex文件還是新解壓的dex文件篮撑,都需要dex文件->new DexFile的過程减细,前面說了在new DexFile時,會調(diào)用DexFile#openDexFile()赢笨,這個方法會進(jìn)行dex->odex的優(yōu)化工作(這里的odex只是對dex進(jìn)行了優(yōu)化未蝌,并不是生成機器碼)驮吱,dex->odex是顯著耗時的,所以即使用了dex文件緩存萧吠,使用mutidex還是會有顯著耗時左冬。有了dex文件后,就會調(diào)用MultiDex#installSecondaryDexes方法纸型,在installSecondaryDexes()方法內(nèi)又碌,會進(jìn)行dex文件->new DexFile->new Element(dexFile)->反射拿到PathClassLoader中類型為DexPathList的成員變量pathList,把前面的element放到DexPathList的dexElements數(shù)組中(這樣hook一下PathClassLoader绊袋,之后項目中使用context.getClassLoader().loadClass(從dex中的類的全名)才不會報classnotFound異常毕匀。context.getClassLoader()得到的就是PathClassLoader)。在new DexFile時癌别,會調(diào)用DexFile#openDexFile()皂岔,這個方法會進(jìn)行dex->odex的優(yōu)化工作,dex->odex是顯著耗時的展姐。

在app運行時躁垛,如果需要某個類,那個類如果還沒有加到內(nèi)存中圾笨,那么會去classLoader的DexPathList中的DexFile數(shù)組中去找教馆,是不是有哪個dexFile含有對應(yīng)的類,如果有擂达,那么就調(diào)用對應(yīng)的dexFile#loadClass()將對應(yīng)的類文件加載到內(nèi)存土铺。如果沒有找到,那么拋ClassNotFound異常板鬓。

ART

android5.0及以上(api對應(yīng)21)的機子是采用的art系統(tǒng)悲敷。在這個系統(tǒng)上安裝apk的時候(art系統(tǒng)吸取了之前的經(jīng)驗),會把apk中所有的dex翻譯成機器碼俭令,預(yù)編譯成多個oat文件(推測:之后會把這些oat文件放到classloader的dexpathlist中)后德。所以有了art系統(tǒng),app啟動的時候抄腔,就不需要在application初始化階段執(zhí)Multidex.install(this);瓢湃。Dalvik系統(tǒng)運行時,如果需要加載某各類赫蛇,需要從classloader中的dexlist中尋找對應(yīng)的字節(jié)碼文件路徑來加載到內(nèi)存绵患,并翻譯成機器碼。而ART系統(tǒng)運行時棍掐,不需要再把字節(jié)碼文件翻譯成機器碼的過程了藏雏,因為在安裝時已經(jīng)把所有dex都翻譯成oat文件了。

但是作煌,雖然在application初始化階段不需要執(zhí)行Multidex.install(this)掘殴。但是如果項目的總方法數(shù)超過了64k,還是需要在構(gòu)建階段把項目達(dá)成多個dex(一個dex文件不能超過64k方法數(shù))。所以在構(gòu)建apk時還是需要mutidex進(jìn)行分包粟誓,5.0及以上機器運行時不再需要mutidex的安裝方案奏寨。

在構(gòu)建apk時,不管minsdk版本是多少鹰服,雖然都需要使用mutidex分包病瞳。但是系統(tǒng)還是做了一些優(yōu)化。如果設(shè)置的minsdk<21,那么在分包時會做很多決策悲酷,這些決策決定哪些類放到主dex套菜,哪些類放到從dex中。這是一個很耗時的決策设易,因為構(gòu)建時間會很長逗柴。如果設(shè)置的minsdk>=21,那么意味著你的apk安裝在android5.0及以上,也就是art系統(tǒng)上(所有的dex在apk安裝時都會翻譯成機器碼顿肺,不需要mutidex.install())戏溺,也就無所謂哪個類在主dex,那個類在從dex了屠尊。所以android gradle 插件(比如3.0.0版本額)在構(gòu)建工程時旷祸,發(fā)現(xiàn)minsdk>=21,就會啟動pre-dexing功能(pre-dexing用于增量編譯讼昆,即下次編譯時托享,只會編譯哪些改變的dex,沒改變的復(fù)用浸赫。工作原理是:事先就把依賴?yán)锩娴膉ar包裝成dex嫌吠,而不需要在構(gòu)建的dex階段去做這個事情,也不再需要計算文件放到哪個dex里的決策掺炭。)辫诅,即把每一個模塊\每一個依賴項都弄成一個dex,不再決策哪些類放到主dex了,哪些放到從dex了(在3.0.0gradle插件中做的更徹底涧狮,pre-dexing進(jìn)化成了per-class dexing炕矮,每個類都是一個dex,這樣如果我只改了一個類者冤,那么只有一個dex需要變化肤视,其他的dex都不用改變,更能加快構(gòu)建時間)涉枫。這樣能減少構(gòu)建時間邢滑。這也為我們減少項目構(gòu)建時間提供了一個思路:可以設(shè)置一個minSdkVersion 21的flavor,專門用于開發(fā)階段的apk構(gòu)建(apk需要運行在5.0及以上機器)愿汰。

    android {
defaultConfig {
    ...
    multiDexEnabled true
}
productFlavors {
    dev {
        // Enable pre-dexing to produce an APK that can be tested on
        // Android 5.0+ without the time-consuming DEX build processes.
        minSdkVersion 21
    }
    prod {
        // The actual minSdkVersion for the production version.
        minSdkVersion 14
    }
}
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                             'proguard-rules.pro'
    }
}
}
dependencies {
    compile 'com.android.support:multidex:1.0.1'
 }   

注解1

java下的ClassLoader

Class clazz=Classloader.loadClass(類全名)困后,其實就是通過一個類的全名乐纸,生成這個類的Class對象。loadClass()內(nèi)部是先進(jìn)行parent.loadClass()讓父類先進(jìn)行加載摇予,如果加載不成功汽绢,再使用該classLoader加載(雙親委派)。然后侧戴,通過findClass(類全名)來加載得到Class對象宁昭。我們?nèi)绻胱远x一個classLoader,那么就是重寫findClass()方法酗宋。在findClass()中积仗,我們拿到我們要加載的路徑(可以是在構(gòu)造方法中提供,也可以在findClass中定義好蜕猫,也可以使用其他方式)寂曹,然后拿到路徑對應(yīng)文件的數(shù)據(jù)流。然后使用classLoader定義好的defineClass(inputStream)來生成Class對象丹锹。所以真正生成class對象的部分稀颁,我們不需要管,調(diào)用一下defineClass(inputStream)就可以了楣黍。我們自定義一個classLoader匾灶,主要就是給它提供一個路徑,然后類全名能找到這個路徑下對應(yīng)的.class文件租漂,然后生成inputStream流阶女。所以自定義一個Classloader也沒什么,不同的ClassLoader并不是根據(jù)流生成class對象的方式不一樣(都是一樣的哩治,通過defineClass(inputstream))秃踩。但是不同的ClassLoader即使加載的同一路徑下的.class文件也是兩個不同的Class對象。

android下的ClassLoader

android下的classloader和java下的有些不一樣业筏。android下的classloader雖然也滿足雙親委派憔杨,但是findClass會拋異常(所以我們不能直接繼承classloader來自定義classLoader)。所以都得使用BaseDexClassLoader蒜胖,BaseDexClassLoader重寫了findClass()消别。其實就是從DexPathList中找dexFile,然后使用dexFile.findClass(className)(native c++方法),所以這里的classloader加載的是dex台谢,不是class字節(jié)碼寻狂。

android經(jīng)常使用的classLoader分為PathClassLoader和DexClassLoader。
PathClassLoader和DexClassLoader都是繼承于BaseDexClassLoader朋沮。BaseDexClassLoader繼承于ClassLoader蛇券。在BaseDexClassLoader中定義了DexPathList。不管是調(diào)用PathClassLoader或DexClassLoader的loadClass(其實真正調(diào)用的BaseDexClassLoader的findClass(classname)方法),其實都是從DexPathList找有沒有對應(yīng)類的路徑(dexpath路徑是在classLoader構(gòu)造方法中傳入的纠亚,然后形成DexPathList)塘慕。如果有,那么dexFile的findClass(classname)來加載生成對應(yīng)的class對象菜枷。如果沒有就報classnotfound異常苍糠。不過注意一下叁丧,DexClassLoader中的pathlist肯定不能訪問PathClassLoade中的pathlist啤誊,因為這是兩個實例。

PathClassLoader以及應(yīng)用

PathClassLoader用來加載系統(tǒng)的和apk中的類拥娄,dexpath路徑在構(gòu)造方法中傳入蚊锹,且只能是系統(tǒng)apk路徑。當(dāng)然也可以像mutidex那樣hookPathClassLoader稚瘾,來改變其內(nèi)部dexPathList成員變量中的元素牡昆,即便是hookPathClassLoader,也是改變系統(tǒng)PathClassLoader的dexPathList成員變量摊欠,而不是new 一個PathClassLoader丢烘,然后傳入從dex的路徑,因為PathClassLoader不允許外界傳入非系統(tǒng)的路徑些椒。(包括從dex中的類播瞳。mutidex做的事情就是把dex一層層封裝dex->odex->dexFile->element,然后把element放到PathClassLoader的pathList中免糕,這樣我們使用的時候context.getClassLoder()進(jìn)行l(wèi)oadclass(類全名)才能加載到從dex中的類赢乓。context.getClassLoder()得到的是PathClassLoader。mutidex沒有使用DexClassLoader的東西石窑。同樣的牌芋,qq空間的熱修復(fù)方案,tink松逊,都是hook的PathClassLoader躺屁。只是qq空間的熱修復(fù)方案把補丁放到dexPathList中數(shù)組的最前面。tink是將補丁和原來的dex合并以后替換原來的dex)经宏。為什么tink不使用DexClassLoader來加載補丁犀暑,而使用PathClassLoader。因為app在運行時烛恤,一般加載類時都是使用的PathClassLoader母怜,比如context.getClassLoader對應(yīng)的就是PathClassLoader。

DexClassLoader以及應(yīng)用

DexClassLoader用來加載外部的類(.jar或.apk)缚柏,外部類的dexpath路徑在構(gòu)造方法中傳入苹熏,比如從網(wǎng)絡(luò)下載的dex等,或插件化的apk(比較robust中加載補丁的時候就是使用的DexClassLoader來加載網(wǎng)絡(luò)的dex。從網(wǎng)絡(luò)下載到dex后轨域。new 一個DexClassLoader(dexpath袱耽,outputName..),new的時候就把網(wǎng)絡(luò)下載到的dex路徑告訴DexClassLoader干发,DexClassLoader會將dex一步步封裝朱巨,放到DexClassLoader中的pathList里面。dex放到DexClassLoader之后枉长,使用DexClassLoader.loadClass(需要加載的patch類全名)得到補丁類的Class對象冀续,然后class.newInstance()對應(yīng)的實例。通過這個實例里的信息來找到要修補的是哪個類必峰,然后找到這個類對應(yīng)的Class對象洪唐,如果沒有就使用PathClassLoader加載。然后將剛才new好的那個補丁實例賦值給這個Class對象中的一個類變量吼蚁。這樣凭需,在app某一處調(diào)用該類的某個方法的時候,會先判斷那個類變量是不是為null肝匆,不為null且確實是需要修復(fù)這個方法粒蜈,那么就使用補丁實例中的邏輯,不再走原來方法的邏輯旗国。其中每個class中添加一個類對象枯怖,每個方法添加一段攔截邏輯是在編譯期操作字節(jié)碼加載的。整體的robust的工作原理就是這樣粗仓〖藁常可以看到robust沒有hook操作PathClassLoader。只是正常使用了DexClassLoader借浊。為什么robust不用hookPathClassLoader塘淑,因為它其實并不是替換類,而是新增加類(邏輯)蚂斤,只是表現(xiàn)形式上看是替換了老方法存捺。所以以前的類并不需要被替換或者置后。)曙蒸。為什么robust不用PathClassLoader加載dex捌治,因為我們不能給PathClassLoader傳入dex的路徑,它必須接收系統(tǒng)的路徑纽窟。

DexClassLoader使用注意事項

注意DexClassLoader(dexpath肖油,outputPath,ClassLoader parent)在進(jìn)行構(gòu)造的時候,需要傳入一個outputPath路徑,它是dexpath路徑下的dex解壓優(yōu)化后的路徑臂港。前面說了森枪,dex->dexFile時视搏,會執(zhí)行opendex(),這里會將dex進(jìn)行優(yōu)化县袱,生成odex浑娜。odex的路徑就是這個outputPath。這樣在正在加載的時候式散,其實是從這個outputPath路徑下加載類文件的筋遭,而不是原來的dexPath。那么注意outputPath路徑需要是app自己的緩存目錄:File dexOutputDir = context.getDir("dex", 0);把這個路徑給到outputPath就可以了暴拄。如果直接指定一個sdcard的緩存路徑漓滔,那么會報錯。PathClassLoader不需要我們管outputPath揍移,傳入null次和。一般我們也不會接觸PathClassLoader的構(gòu)造反肋。詳情可看這里
注意DexClassLoader中還要傳入一個ClassLoader作為該DexClassLoader的父類那伐。這樣,我們使用DexClassLoader加載一個類時石蔗,根據(jù)雙親委派罕邀,會先讓父類classloader進(jìn)行加載。父類加載不了养距,再使用該DexClassLoader加載诉探。一般我們使用getClassLoader()即app的context對應(yīng)的classLoader:PathClassLoader作為DexClassLoader的父類。這樣也就解釋了為什么robust補丁的類和app中的相同類沒有沖突棍厌,因為都是使用context對應(yīng)的classLoader加載的那些肾胯。

參考文章:

Android類加載之PathClassLoader和DexClassLoader

【Android高級】DexClassloader和PathClassloader動態(tài)加載插件的實現(xiàn)

配置方法數(shù)超過 64K 的應(yīng)用

MultiDex工作原理分析和優(yōu)化方案

Dalvik,ART與ODEX相愛相生

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耘纱,隨后出現(xiàn)的幾起案子敬肚,更是在濱河造成了極大的恐慌,老刑警劉巖束析,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艳馒,死亡現(xiàn)場離奇詭異,居然都是意外死亡员寇,警方通過查閱死者的電腦和手機弄慰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝶锋,“玉大人陆爽,你說我怎么就攤上這事“饴疲” “怎么了慌闭?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵恶守,是天一觀的道長。 經(jīng)常有香客問我贡必,道長兔港,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任仔拟,我火速辦了婚禮衫樊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘利花。我一直安慰自己科侈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布炒事。 她就那樣靜靜地躺著臀栈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挠乳。 梳的紋絲不亂的頭發(fā)上权薯,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音睡扬,去河邊找鬼盟蚣。 笑死,一個胖子當(dāng)著我的面吹牛卖怜,可吹牛的內(nèi)容都是我干的屎开。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼马靠,長吁一口氣:“原來是場噩夢啊……” “哼奄抽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甩鳄,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤逞度,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娩贷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第晰,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年彬祖,在試婚紗的時候發(fā)現(xiàn)自己被綠了茁瘦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡储笑,死狀恐怖甜熔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情突倍,我是刑警寧澤腔稀,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布盆昙,位于F島的核電站,受9級特大地震影響焊虏,放射性物質(zhì)發(fā)生泄漏淡喜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一诵闭、第九天 我趴在偏房一處隱蔽的房頂上張望炼团。 院中可真熱鬧,春花似錦疏尿、人聲如沸瘟芝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锌俱。三九已至,卻和暖如春敌呈,著一層夾襖步出監(jiān)牢的瞬間贸宏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工驱富, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锚赤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓褐鸥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赐稽。 傳聞我的和親對象是個殘疾皇子叫榕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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