PathClassLoader,DexClassLoader,mutidex,熱修復(fù),art ,dalvik總結(jié)

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

地址:http://www.itdecent.cn/p/54378566aa86

目錄

一直對(duì)一些概念,知道的一知半解。也看過記住過,不過過后又忘了。這里做一些總結(jié)??梢苑窒?,也可以自己以后查閱。源碼什么的其實(shí)都不是很難,之前分析過mutidex的源碼,也懶得貼了。這個(gè)博客只是對(duì)一些概念的總結(jié)。源碼網(wǎng)上其實(shí)很多,可以如果需要,可以自己找找哈~。最后我也貼了一些。

Dalvik

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

在app啟動(dòng)后,application#attachBaseContext()時(shí),mutidex會(huì)檢查是不是之前已經(jīng)有了緩存的從dex 文件,如果沒有,那么就從apk中解壓出來那些從dex(耗時(shí))文件。如果有,那么就使用這些緩存的從dex文件。注意緩存的是dex文件,不是DexFile。所以每次冷啟動(dòng)app時(shí),不管用的是緩存的dex文件還是新解壓的dex文件,都需要dex文件->new DexFile的過程,前面說了在new DexFile時(shí),會(huì)調(diào)用DexFile#openDexFile(),這個(gè)方法會(huì)進(jìn)行dex->odex的優(yōu)化工作(這里的odex只是對(duì)dex進(jìn)行了優(yōu)化,并不是生成機(jī)器碼),dex->odex是顯著耗時(shí)的,所以即使用了dex文件緩存,使用mutidex還是會(huì)有顯著耗時(shí)。有了dex文件后,就會(huì)調(diào)用MultiDex#installSecondaryDexes方法,在installSecondaryDexes()方法內(nèi),會(huì)進(jìn)行dex文件->new DexFile->new Element(dexFile)->反射拿到PathClassLoader中類型為DexPathList的成員變量pathList,把前面的element放到DexPathList的dexElements數(shù)組中(這樣hook一下PathClassLoader,之后項(xiàng)目中使用context.getClassLoader().loadClass(從dex中的類的全名)才不會(huì)報(bào)classnotFound異常。context.getClassLoader()得到的就是PathClassLoader)。在new DexFile時(shí),會(huì)調(diào)用DexFile#openDexFile(),這個(gè)方法會(huì)進(jìn)行dex->odex的優(yōu)化工作,dex->odex是顯著耗時(shí)的。

在app運(yùn)行時(shí),如果需要某個(gè)類,那個(gè)類如果還沒有加到內(nèi)存中,那么會(huì)去classLoader的DexPathList中的DexFile數(shù)組中去找,是不是有哪個(gè)dexFile含有對(duì)應(yīng)的類,如果有,那么就調(diào)用對(duì)應(yīng)的dexFile#loadClass()將對(duì)應(yīng)的類文件加載到內(nèi)存。如果沒有找到,那么拋ClassNotFound異常。

ART

android5.0及以上(api對(duì)應(yīng)21)的機(jī)子是采用的art系統(tǒng)。在這個(gè)系統(tǒng)上安裝apk的時(shí)候(art系統(tǒng)吸取了之前的經(jīng)驗(yàn)),會(huì)把a(bǔ)pk中所有的dex翻譯成機(jī)器碼,預(yù)編譯成多個(gè)oat文件(推測:之后會(huì)把這些oat文件放到classloader的dexpathlist中)。所以有了art系統(tǒng),app啟動(dòng)的時(shí)候,就不需要在application初始化階段執(zhí)Multidex.install(this);。Dalvik系統(tǒng)運(yùn)行時(shí),如果需要加載某各類,需要從classloader中的dexlist中尋找對(duì)應(yīng)的字節(jié)碼文件路徑來加載到內(nèi)存,并翻譯成機(jī)器碼。而ART系統(tǒng)運(yùn)行時(shí),不需要再把字節(jié)碼文件翻譯成機(jī)器碼的過程了,因?yàn)樵诎惭b時(shí)已經(jīng)把所有dex都翻譯成oat文件了。

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

在構(gòu)建apk時(shí),不管minsdk版本是多少,雖然都需要使用mutidex分包。但是系統(tǒng)還是做了一些優(yōu)化。如果設(shè)置的minsdk<21,那么在分包時(shí)會(huì)做很多決策,這些決策決定哪些類放到主dex,哪些類放到從dex中。這是一個(gè)很耗時(shí)的決策,因?yàn)闃?gòu)建時(shí)間會(huì)很長。如果設(shè)置的minsdk>=21,那么意味著你的apk安裝在android5.0及以上,也就是art系統(tǒng)上(所有的dex在apk安裝時(shí)都會(huì)翻譯成機(jī)器碼,不需要mutidex.install()),也就無所謂哪個(gè)類在主dex,那個(gè)類在從dex了。所以android gradle 插件(比如3.0.0版本額)在構(gòu)建工程時(shí),發(fā)現(xiàn)minsdk>=21,就會(huì)啟動(dòng)pre-dexing功能(pre-dexing用于增量編譯,即下次編譯時(shí),只會(huì)編譯哪些改變的dex,沒改變的復(fù)用。工作原理是:事先就把依賴?yán)锩娴膉ar包裝成dex,而不需要在構(gòu)建的dex階段去做這個(gè)事情,也不再需要計(jì)算文件放到哪個(gè)dex里的決策。),即把每一個(gè)模塊\每一個(gè)依賴項(xiàng)都弄成一個(gè)dex,不再?zèng)Q策哪些類放到主dex了,哪些放到從dex了(在3.0.0gradle插件中做的更徹底,pre-dexing進(jìn)化成了per-class dexing,每個(gè)類都是一個(gè)dex,這樣如果我只改了一個(gè)類,那么只有一個(gè)dex需要變化,其他的dex都不用改變,更能加快構(gòu)建時(shí)間)。這樣能減少構(gòu)建時(shí)間。這也為我們減少項(xiàng)目構(gòu)建時(shí)間提供了一個(gè)思路:可以設(shè)置一個(gè)minSdkVersion 21的flavor,專門用于開發(fā)階段的apk構(gòu)建(apk需要運(yùn)行在5.0及以上機(jī)器)。

    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(類全名),其實(shí)就是通過一個(gè)類的全名,生成這個(gè)類的Class對(duì)象。loadClass()內(nèi)部是先進(jìn)行parent.loadClass()讓父類先進(jìn)行加載,如果加載不成功,再使用該classLoader加載(雙親委派)。然后,通過findClass(類全名)來加載得到Class對(duì)象。我們?nèi)绻胱远x一個(gè)classLoader,那么就是重寫findClass()方法。在findClass()中,我們拿到我們要加載的路徑(可以是在構(gòu)造方法中提供,也可以在findClass中定義好,也可以使用其他方式),然后拿到路徑對(duì)應(yīng)文件的數(shù)據(jù)流。然后使用classLoader定義好的defineClass(inputStream)來生成Class對(duì)象。所以真正生成class對(duì)象的部分,我們不需要管,調(diào)用一下defineClass(inputStream)就可以了。我們自定義一個(gè)classLoader,主要就是給它提供一個(gè)路徑,然后類全名能找到這個(gè)路徑下對(duì)應(yīng)的.class文件,然后生成inputStream流。所以自定義一個(gè)Classloader也沒什么,不同的ClassLoader并不是根據(jù)流生成class對(duì)象的方式不一樣(都是一樣的,通過defineClass(inputstream))。但是不同的ClassLoader即使加載的同一路徑下的.class文件也是兩個(gè)不同的Class對(duì)象。

android下的ClassLoader

android下的classloader和java下的有些不一樣。android下的classloader雖然也滿足雙親委派,但是findClass會(huì)拋異常(所以我們不能直接繼承classloader來自定義classLoader)。所以都得使用BaseDexClassLoader,BaseDexClassLoader重寫了findClass()。其實(shí)就是從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(其實(shí)真正調(diào)用的BaseDexClassLoader的findClass(classname)方法),其實(shí)都是從DexPathList找有沒有對(duì)應(yīng)類的路徑(dexpath路徑是在classLoader構(gòu)造方法中傳入的,然后形成DexPathList)。如果有,那么dexFile的findClass(classname)來加載生成對(duì)應(yīng)的class對(duì)象。如果沒有就報(bào)classnotfound異常。不過注意一下,DexClassLoader中的pathlist肯定不能訪問PathClassLoade中的pathlist,因?yàn)檫@是兩個(gè)實(shí)例。

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 一個(gè)PathClassLoader,然后傳入從dex的路徑,因?yàn)镻athClassLoader不允許外界傳入非系統(tǒng)的路徑。(包括從dex中的類。mutidex做的事情就是把dex一層層封裝dex->odex->dexFile->element,然后把element放到PathClassLoader的pathList中,這樣我們使用的時(shí)候context.getClassLoder()進(jìn)行l(wèi)oadclass(類全名)才能加載到從dex中的類。context.getClassLoder()得到的是PathClassLoader。mutidex沒有使用DexClassLoader的東西。同樣的,qq空間的熱修復(fù)方案,tink,都是hook的PathClassLoader。只是qq空間的熱修復(fù)方案把補(bǔ)丁放到dexPathList中數(shù)組的最前面。tink是將補(bǔ)丁和原來的dex合并以后替換原來的dex)。為什么tink不使用DexClassLoader來加載補(bǔ)丁,而使用PathClassLoader。因?yàn)閍pp在運(yùn)行時(shí),一般加載類時(shí)都是使用的PathClassLoader,比如context.getClassLoader對(duì)應(yīng)的就是PathClassLoader。

DexClassLoader以及應(yīng)用

DexClassLoader用來加載外部的類(.jar或.apk),外部類的dexpath路徑在構(gòu)造方法中傳入,比如從網(wǎng)絡(luò)下載的dex等,或插件化的apk(比較robust中加載補(bǔ)丁的時(shí)候就是使用的DexClassLoader來加載網(wǎng)絡(luò)的dex。從網(wǎng)絡(luò)下載到dex后。new 一個(gè)DexClassLoader(dexpath,outputName..),new的時(shí)候就把網(wǎng)絡(luò)下載到的dex路徑告訴DexClassLoader,DexClassLoader會(huì)將dex一步步封裝,放到DexClassLoader中的pathList里面。dex放到DexClassLoader之后,使用DexClassLoader.loadClass(需要加載的patch類全名)得到補(bǔ)丁類的Class對(duì)象,然后class.newInstance()對(duì)應(yīng)的實(shí)例。通過這個(gè)實(shí)例里的信息來找到要修補(bǔ)的是哪個(gè)類,然后找到這個(gè)類對(duì)應(yīng)的Class對(duì)象,如果沒有就使用PathClassLoader加載。然后將剛才new好的那個(gè)補(bǔ)丁實(shí)例賦值給這個(gè)Class對(duì)象中的一個(gè)類變量。這樣,在app某一處調(diào)用該類的某個(gè)方法的時(shí)候,會(huì)先判斷那個(gè)類變量是不是為null,不為null且確實(shí)是需要修復(fù)這個(gè)方法,那么就使用補(bǔ)丁實(shí)例中的邏輯,不再走原來方法的邏輯。其中每個(gè)class中添加一個(gè)類對(duì)象,每個(gè)方法添加一段攔截邏輯是在編譯期操作字節(jié)碼加載的。整體的robust的工作原理就是這樣??梢钥吹絩obust沒有hook操作PathClassLoader。只是正常使用了DexClassLoader。為什么robust不用hookPathClassLoader,因?yàn)樗鋵?shí)并不是替換類,而是新增加類(邏輯),只是表現(xiàn)形式上看是替換了老方法。所以以前的類并不需要被替換或者置后。)。為什么robust不用PathClassLoader加載dex,因?yàn)槲覀儾荒芙oPathClassLoader傳入dex的路徑,它必須接收系統(tǒng)的路徑。

DexClassLoader使用注意事項(xiàng)

注意DexClassLoader(dexpath,outputPath,ClassLoader parent)在進(jìn)行構(gòu)造的時(shí)候,需要傳入一個(gè)outputPath路徑,它是dexpath路徑下的dex解壓優(yōu)化后的路徑。前面說了,dex->dexFile時(shí),會(huì)執(zhí)行opendex(),這里會(huì)將dex進(jìn)行優(yōu)化,生成odex。odex的路徑就是這個(gè)outputPath。這樣在正在加載的時(shí)候,其實(shí)是從這個(gè)outputPath路徑下加載類文件的,而不是原來的dexPath。那么注意outputPath路徑需要是app自己的緩存目錄:File dexOutputDir = context.getDir("dex", 0);把這個(gè)路徑給到outputPath就可以了。如果直接指定一個(gè)sdcard的緩存路徑,那么會(huì)報(bào)錯(cuò)。PathClassLoader不需要我們管outputPath,傳入null。一般我們也不會(huì)接觸PathClassLoader的構(gòu)造。詳情可看這里
注意DexClassLoader中還要傳入一個(gè)ClassLoader作為該DexClassLoader的父類。這樣,我們使用DexClassLoader加載一個(gè)類時(shí),根據(jù)雙親委派,會(huì)先讓父類classloader進(jìn)行加載。父類加載不了,再使用該DexClassLoader加載。一般我們使用getClassLoader()即app的context對(duì)應(yīng)的classLoader:PathClassLoader作為DexClassLoader的父類。這樣也就解釋了為什么robust補(bǔ)丁的類和app中的相同類沒有沖突,因?yàn)槎际鞘褂胏ontext對(duì)應(yīng)的classLoader加載的那些。

參考文章:

Android類加載之PathClassLoader和DexClassLoader

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

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

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

Dalvik,ART與ODEX相愛相生

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容