本文取自本人CSDN的博客:
http://blog.csdn.net/gaozhan_csdn/article/details/52085911
話說(shuō)簡(jiǎn)書(shū)不是很適合代碼編寫啊妻往,好麻煩几迄。
參考資料: 《深入理解Java虛擬機(jī)》 -周志明 Android中的動(dòng)態(tài)加載機(jī)制
本篇不深入涉及Java類加載器,如果想更深入了解正罢,可以看一下這篇博客http://blog.csdn.net/zhoudaxia/article/details/35824249
前言:動(dòng)態(tài)加載在應(yīng)用開(kāi)發(fā)中有著很重要的地位,當(dāng)我們項(xiàng)目越來(lái)越大驻民,我們可以通過(guò)插件化來(lái)減少應(yīng)用的內(nèi)存翻具,然后動(dòng)態(tài)加載那些插件。還有一個(gè)方面回还,如果我們的應(yīng)用頻繁的更新裆泳,頻繁的發(fā)布新版本,肯定會(huì)造成用戶體驗(yàn)下降柠硕,那么我們可以用動(dòng)態(tài)加載技術(shù)在不發(fā)布新版本的情況下更新一些模塊工禾。
那么既然要用動(dòng)態(tài)加載,就肯定涉及到類加載器蝗柔,我們先看一下Java中的類加載器闻葵。
一、Java中類加載器
在這里癣丧,我們不去說(shuō)類加載的具體過(guò)程笙隙,只是總結(jié)一下Java中類加載器與雙親委派模型。
1.類加載器與類本身確定類的唯一性
對(duì)于一個(gè)類坎缭,這個(gè)類本身和加載它的類加載器共同確定其在虛擬機(jī)中的唯一性竟痰。 我們使用兩個(gè)類加載器進(jìn)行加載同一個(gè)類,那么這兩個(gè)類是不相等的掏呼,那么虛擬機(jī)中會(huì)存在兩個(gè)同名的類坏快。
2.Java三種預(yù)定義類型類加載器
啟動(dòng)類加載器(Bootstrap ClassLoader,也稱為引導(dǎo)類加載器)
該類加載器負(fù)責(zé)將存放在\lib目錄中憎夷,或者被-Xbootclasspath參數(shù)所指定的路徑中的莽鸿,并且是虛擬機(jī)識(shí)別的(這點(diǎn)很重要)類加載到虛擬機(jī)中。
加載虛擬機(jī)識(shí)別的這一點(diǎn)與雙親委派模型配合很重要。在下面介紹祥得,先留意這一點(diǎn)兔沃。
對(duì)于啟動(dòng)類加載器還有一點(diǎn)需要注意,也是和雙親委派模型有關(guān)级及,如果我們自定義一個(gè)類加載器乒疏,想把一個(gè)加載請(qǐng)求委派給啟動(dòng)類加載器,只需要使用null替代即可(可以看下面的findClass()方法中的代碼實(shí)現(xiàn))饮焦。
開(kāi)發(fā)者不可以直接使用該加載器怕吴。擴(kuò)展類加載器(Extension ClassLoader)
該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載\lib\ext目錄中的县踢,或者被java.ext.dirs系統(tǒng)變量所指定的路程中的所有類庫(kù)转绷。
開(kāi)發(fā)者可以直接使用該加載器。應(yīng)用程序類加載器(Application ClassLoader硼啤,也稱為系統(tǒng)類加載器)
該類加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)议经。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統(tǒng)類加載器谴返。該加載器負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù)煞肾。
開(kāi)發(fā)者可以直接使用這個(gè)類加載器。如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器亏镰,一般情況下該加載器是程序中的默認(rèn)的類加載器扯旷。
3.雙親委派模型
上圖顯示了類加載器之間的層次關(guān)系,被稱為類加載器的雙親委派模型索抓。
除了頂層的啟動(dòng)類加載器外钧忽,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。
那么根據(jù)上圖逼肯,如果一個(gè)類加載器收到了類加載的請(qǐng)求耸黑,它首先不會(huì)自己嘗試加載這個(gè)類,而是把請(qǐng)求委派給父類加載器去加載篮幢,每一個(gè)層次的類加載器都是這樣大刊,那么所有的請(qǐng)求其實(shí)最后都是會(huì)到啟動(dòng)類加載器中,如果父類加載器反饋無(wú)法加載三椿,子加載器才會(huì)嘗試自己加載缺菌。
實(shí)現(xiàn)雙親委派模型的代碼在loadClass()方法中:
4.雙親委派模型的優(yōu)點(diǎn)
大家都知道Object類是個(gè)基礎(chǔ)類,如果我們自己寫了一個(gè)Object類搜锰,那么如果沒(méi)有雙親委派模型的話伴郁,再加上我們并沒(méi)有用啟動(dòng)類加載器去加載我們寫的這個(gè)Object類的話,系統(tǒng)中會(huì)存在兩個(gè)Object類(參考上述的類在虛擬機(jī)中的唯一性)蛋叼。那么有了雙親委派模型焊傅,我們寫了一個(gè)Object類剂陡,會(huì)先去檢查它是否加載了(肯定已經(jīng)加載了),那么我們寫的這個(gè)就不會(huì)被重復(fù)加載狐胎,也就保證了基礎(chǔ)類的唯一性鸭栖,防止混亂破壞。就算沒(méi)有檢查握巢,根據(jù)上面關(guān)于啟動(dòng)類加載器的介紹晕鹊,必須是虛擬機(jī)識(shí)別的,Object存放在rt.jar中镜粤,我們寫的不會(huì)被識(shí)別捏题。
那么從另一個(gè)方面玻褪,基礎(chǔ)類Object怎么保證的在任何環(huán)境下都是同一個(gè)類(即加載器在任何情況下都是同一個(gè))肉渴,這就是雙親委派模型的作用了,每次這個(gè)加載請(qǐng)求都會(huì)委派給處于最頂端的啟動(dòng)類加載器進(jìn)行加載带射,虛擬機(jī)識(shí)別rt.jar同规,那么就保證了每次都是由啟動(dòng)類加載器加載的Object,那么根據(jù)第1條的唯一性窟社,這樣做就保證了Object的唯一性券勺。
5.自定義類加載器符合雙親委派模型
我們根據(jù)上面的介紹知道,雙親委派模型的邏輯都在loadClass()方法中灿里,那么我們?yōu)榱瞬黄茐碾p親委派模型关炼,自定義類加載器時(shí)不去重寫loadClass()方法,而是重寫findClass()方法匣吊,將自己的類加載邏輯寫到findClass()方法中儒拂,在loadClass()方法中,最后父類加載器無(wú)法加載的時(shí)候色鸳,調(diào)用的就是findClass()方法社痛。這樣我們就保證了我們自定義的類加載器是符合雙親委派模型的。如果重寫loadClass()方法命雀,會(huì)出現(xiàn)一系列錯(cuò)誤蒜哀,比如基礎(chǔ)類加載不上等。
findClass()方法是在JDK1.2以后引入的吏砂。
5.defineClass()方法
//定義類型撵儿,一般在findClass方法中讀取到對(duì)應(yīng)字節(jié)碼后調(diào)用,final狐血,不可繼承淀歇,一般不用重寫,直接調(diào)用氛雪。在findClass()方法中調(diào)用房匆。
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }
介紹這個(gè)是為了注明下面的Android類加載與Java中類加載的區(qū)別。
二、Android類加載器
前面介紹的是Java中的類加載器浴鸿,我們知道android中的虛擬機(jī)是Dalvik井氢,它不是標(biāo)準(zhǔn)的Java虛擬機(jī),所以在類加載機(jī)制上岳链,和Java中的類加載器有一些區(qū)別花竞。
我們?cè)趈ava標(biāo)準(zhǔn)的虛擬機(jī)中,如果自定義類加載器掸哑,會(huì)繼承ClassLoader约急,并重寫findClass()方法,在內(nèi)部調(diào)用defineClass()去從一個(gè)二進(jìn)制流中加載Class苗分。那么在Android中厌蔽,這個(gè)defineClass()方法去調(diào)用VMClassLoader的defineClass本地靜態(tài)方法,而這個(gè)方法內(nèi)部除了拋出了異乘ぱⅲ“UnsupportedOperationException”還有一些屬性值奴饮,其他什么都沒(méi)做。在博客開(kāi)頭寫的鏈接中有源碼择浊,這就不貼了戴卜。
那么在Dalvik虛擬中,動(dòng)態(tài)加載類就需要另外由ClassLoader派生出的兩個(gè)類:DexClassLoader和PathClassLoader琢岩。
這兩個(gè)類重載了ClassLoader的findClass()方法投剥,并沒(méi)有重寫loadClass()方法,所以這兩個(gè)類加載器符合雙親委派模型担孔。
在介紹這兩個(gè)類加載器區(qū)別之前江锨,說(shuō)明兩點(diǎn)
(1)Dalvik虛擬機(jī)識(shí)別的是dex文件,而不是class文件攒磨,因此泳桦,我們加載的是dex文件,或者包含dex文件的apk文件或jar文件娩缰。
(2)DexFile類灸撰。上述兩個(gè)類加載器都是通過(guò)DexFile這個(gè)類去加載類。
區(qū)別: PathClassLoader不能主動(dòng)從zip包中釋放出dex拼坎,所以只支持直接操作dex格式文件浮毯,或者已經(jīng)安裝的apk(已經(jīng)安裝的apk在cache中存在緩存的dex文件)。而DexClassLoader可以支持.apk泰鸡、.jar和.dex文件债蓝,并且會(huì)在指定的outpath路徑釋放出dex文件。
下面會(huì)有加載的demo盛龄。
加載好類以后饰迹,我們可以通過(guò)反射來(lái)使用加載好的類芳誓,但過(guò)多的使用反射會(huì)有一定的性能開(kāi)銷,代碼復(fù)雜凌亂啊鸭。我們還有一種方式锹淌,即接口。我們可以將一些方法提取出來(lái)作為一個(gè)接口赠制,將待加載的類實(shí)現(xiàn)這個(gè)接口赂摆,在寫待加載的類時(shí),注意要有一個(gè)參數(shù)為空的構(gòu)造函數(shù)钟些,我們?cè)谥鞔a中就能將類對(duì)象強(qiáng)制轉(zhuǎn)為接口對(duì)象烟号,直接調(diào)用成員方法。
三政恍、Demo
這個(gè)Demo是Android中的動(dòng)態(tài)加載機(jī)制里的汪拥,為了展示,簡(jiǎn)略了一下抚垃,并且由于android系統(tǒng)的變更喷楣,其中有一些小坑趟大,我們需要改一下代碼鹤树。
1.接口與待加載類的實(shí)現(xiàn)與處理
- 接口代碼
public interface IDynamic {
void init(Activity var1);
//彈出消息
void showSomething();
void destory();
}
- 待加載類
-
接口的處理
將接口導(dǎo)出一個(gè)jar包(由于AS導(dǎo)包得修改Gradle,嫌麻煩加上電腦上有Eclipse for Android 逊朽,我用它打的包):
這里寫圖片描述
然后將接口的jar包放入AS工程里的libs目錄下罕伯。
-
待加載類的處理
注意:上面dex和output前是兩個(gè)‘-’ 岛蚤,csdn顯示的是一個(gè)邑狸,只不過(guò)長(zhǎng)度長(zhǎng)一點(diǎn),坑了我一會(huì)涤妒。 如下圖:
將待加載類按上述方式打包,將打好的jar包復(fù)制到SDK的build-tools目錄下叽讳,打開(kāi)命令行追他,進(jìn)入build-tools目錄,輸入:
dx –dex –output dynamic1.apk dynamic.jar
這里寫圖片描述
經(jīng)過(guò)上述步驟单雾,build-tools目錄下就會(huì)生成一個(gè)名為dynamic1.apk的文件,那么上述步驟究竟做了什么她紫?
我們知道DexClassLoader和PathClassLoader這兩個(gè)類加載器加載的并不是class文件硅堆,而是將class文件優(yōu)化后的dex文件,上述步驟便是將jar包解壓贿讹,將其中的class文件優(yōu)化成dex文件渐逃,然后壓縮為apk文件。
- 目標(biāo)類的實(shí)現(xiàn)與處理
- AndroidManifest的處理
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.dynamicloading"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter> <action android:name="android.intent.action.MAIN"/>
//注意民褂,和目標(biāo)類中相對(duì)應(yīng)
<action android:name="com.example.impl"/>
<category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
</activity>
</application>
</manifest>
- 將dynamic1.apk放入相應(yīng)目錄
好了茄菊,處理的差不多了疯潭,該處理我們?cè)诖虞d類處理那一步中生成的dynamic1.apk了。因?yàn)橛肈exClassLoader按照Android中的動(dòng)態(tài)加載機(jī)制中的方法我并沒(méi)有成功面殖,因?yàn)椴┲魇菍ar包放進(jìn)SD卡中袁勺,現(xiàn)在好像并不支持這樣做,所以這次我打算用PathClassLoader畜普,看了上面介紹的小伙伴已經(jīng)知道期丰,PathClassLoader只能識(shí)別dex文件和已經(jīng)安裝好的apk文件(安裝好的會(huì)有相應(yīng)的緩存dex文件),那么我們接下來(lái)就要想辦法把咱們生成的apk文件放到一個(gè)目錄下吃挑,放在哪個(gè)目錄下呢钝荡?
一開(kāi)始我也不清楚,不過(guò)我將下面這個(gè)屬性用Toast顯示了一下舶衬,便得到了目錄 String apkPath = actInfo.applicationInfo.sourceDir;
要放的目錄是: /data/app/com.example.administrator.dynamicloadi ng-1
這里需要說(shuō)明一點(diǎn)埠通,如果你用的是真機(jī)的話,可能需要root權(quán)限才能進(jìn)去這些目錄逛犹,我用的模擬器端辱,不需要權(quán)限。
還有一個(gè)坑虽画,這個(gè)項(xiàng)目需要先運(yùn)行一下舞蔽,app目錄下才會(huì)生成com.example.administrator.dynamicloading-1這個(gè)文件(當(dāng)然你生成的不一定是這個(gè),取決于的你的包名)
注意:下面的一些命令都有一個(gè)前提码撰,需要開(kāi)啟模擬器渗柿。或者連著真機(jī)脖岛。
項(xiàng)目運(yùn)行完后朵栖,你可以使用以下命令看看有沒(méi)有相應(yīng)包名的文件:
adb shell進(jìn)入模擬器,然后cd /data/app進(jìn)入app目錄柴梆,然后ls命令查看一下該目錄中都有哪些文件陨溅。 我要進(jìn)入的目錄如下(因?yàn)樯厦婺莻€(gè)屬性Toast顯示的是它):
好了,那么用命令行將apk文件放入這個(gè)目錄绍在,先使用exit命令退出模擬器门扇。
接下來(lái),用命令將dynamic1.apk放入這個(gè)目錄 命令如下: adb push apk所在目錄 要放入的目錄
例如我的apk在剛才的build-tools目錄下揣苏,要放進(jìn)/data/app/com.example.administrator.dynamicloading-1悯嗓,所以命令如下圖:
如果想查看有沒(méi)有復(fù)制成功,可以按照上述的方法檢查一下卸察,adb shell 脯厨,然后進(jìn)入這個(gè)目錄,ls一下坑质。
成功加載了。