Android動(dòng)態(tài)加載學(xué)習(xí)總結(jié)(一):類加載器

本文取自本人CSDN的博客:
http://blog.csdn.net/gaozhan_csdn/article/details/52085911
話說(shuō)簡(jiǎn)書不是很適合代碼編寫啊,好麻煩。

參考資料: 《深入理解Java虛擬機(jī)》 -周志明 Android中的動(dòng)態(tài)加載機(jī)制
本篇不深入涉及Java類加載器,如果想更深入了解,可以看一下這篇博客http://blog.csdn.net/zhoudaxia/article/details/35824249
前言:動(dòng)態(tài)加載在應(yīng)用開發(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))。
    開發(fā)者不可以直接使用該加載器。

  • 擴(kuò)展類加載器(Extension ClassLoader)
    該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路程中的所有類庫(kù)。
    開發(fā)者可以直接使用該加載器。

  • 應(yīng)用程序類加載器(Application ClassLoader,也稱為系統(tǒng)類加載器)
    該類加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統(tǒng)類加載器。該加載器負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù)。
    開發(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()方法中:

jianshu1.PNG

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)做。在博客開頭寫的鏈接中有源碼,這就不貼了。
那么在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ì)有一定的性能開銷,代碼復(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();
}

  • 待加載類
jianshu2.PNG
  • 接口的處理
    將接口導(dǎo)出一個(gè)jar包(由于AS導(dǎo)包得修改Gradle,嫌麻煩加上電腦上有Eclipse for Android ,我用它打的包):


    這里寫圖片描述

    然后將接口的jar包放入AS工程里的libs目錄下。

  • 待加載類的處理
    將待加載類按上述方式打包,將打好的jar包復(fù)制到SDK的build-tools目錄下,打開命令行,進(jìn)入build-tools目錄,輸入:
    dx –dex –output dynamic1.apk dynamic.jar

    注意:上面dex和output前是兩個(gè)‘-’ ,csdn顯示的是一個(gè),只不過(guò)長(zhǎng)度長(zhǎng)一點(diǎn),坑了我一會(huì)。 如下圖:
    這里寫圖片描述

    經(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)與處理
3.PNG
4.PNG
  • 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è)目錄下呢?

一開始我也不清楚,不過(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è)前提,需要開啟模擬器。或者連著真機(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一下。

好了,運(yùn)行項(xiàng)目,看看結(jié)果:
這里寫圖片描述

成功加載了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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