Android動(dòng)態(tài)加載Dex機(jī)制解析

什么是類加載器?

類加載器(class loader)是 Java?中的一個(gè)很重要的概念陈症。類加載器負(fù)責(zé)加載 Java 類的字節(jié)代碼到 Java 虛擬機(jī)中。Java 虛擬機(jī)使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼凡纳,并轉(zhuǎn)換成java.lang.Class類的一個(gè)實(shí)例。每個(gè)這樣的實(shí)例用來(lái)表示一個(gè) Java 類帝蒿。通過此實(shí)例的 newInstance()方法就可以創(chuàng)建出該類的一個(gè)對(duì)象荐糜。實(shí)際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過工具動(dòng)態(tài)生成的葛超,也可能是通過網(wǎng)絡(luò)下載的暴氏。基本上所有的類加載器都是 java.lang.ClassLoader類的一個(gè)實(shí)例巩掺,需要了解ClassLoader可以參考這篇文章深入ClasssLoader

Dalvik虛擬機(jī)類加載機(jī)制

Dalvik虛擬機(jī)如同其他Java虛擬機(jī)一樣偏序,在運(yùn)行程序時(shí)首先需要將對(duì)應(yīng)的類加載到內(nèi)存中。而在Java標(biāo)準(zhǔn)的虛擬機(jī)中胖替,類加載可以從class文件中讀取研儒,也可以是其他形式的二進(jìn)制流,因此独令,我們常常利用這一點(diǎn)端朵,在程序運(yùn)行時(shí)手動(dòng)加載Class,從而達(dá)到代碼動(dòng)態(tài)加載執(zhí)行的目的燃箭,但是Dalvik虛擬機(jī)畢竟不算是標(biāo)準(zhǔn)的Java虛擬機(jī)冲呢,因此在類加載機(jī)制上,它們有相同的地方招狸,也有不同之處敬拓。

我們先看下下面這張關(guān)于Android Classload機(jī)制的圖。

與JVM不同裙戏,Dalvik的虛擬機(jī)不能用ClassCload直接加載.dex乘凸,Android從ClassLoader派生出了兩個(gè)類:DexClassLoaderPathClassLoader;而這兩個(gè)類就是我們加載dex文件的關(guān)鍵累榜,這兩者的區(qū)別是:

1.DexClassLoader:可以加載jar/apk/dex营勤,可以從SD卡中加載未安裝的apk;2.PathClassLoader:要傳入系統(tǒng)中apk的存放Path壹罚,所以只能加載已經(jīng)安裝的apk文件葛作。

關(guān)于Android 動(dòng)態(tài)加載基礎(chǔ) ClassLoader工作機(jī)制大家可以參考這里:http://www.reibang.com/p/56021ff40c9d

準(zhǔn)備工作開始

一猖凛、打開Android studio 新建工程:


工程目錄是這樣的:


動(dòng)態(tài)加載進(jìn)來(lái)的class如何使用赂蠢,一般有2種辦法,一種是使用反射調(diào)用形病,這種我不多做介紹客年;還有一種是使用接口編程的方式來(lái)調(diào)用對(duì)應(yīng)的方法霞幅,畢竟.dex文件也是我們自己維護(hù)的,所以可以把方法抽象成公共接口量瓜,把這些接口也復(fù)制到主項(xiàng)目里面去司恳,就可以通過這些接口調(diào)用動(dòng)態(tài)加載得到的實(shí)例的方法了。
接下來(lái)我們?cè)创a包下面新建一個(gè)包名稱是dynamic绍傲,然后在dynamic下新建一個(gè)interface接口Dynamic扔傅,里面有個(gè)接口方法,就叫sayHello()吧烫饼,返回一個(gè)String猎塞,到時(shí)候我們可以通過Toast彈出來(lái),Dynamic.java:

package wangyang.zun.com.mydexdemo.dynamic;  
public interface Dynamic {  
    String sayHello();  
}  

接著我們新建一個(gè)impl包杠纵,并實(shí)現(xiàn)Dynamic接口荠耽,DynamicImpl.java:

package wangyang.zun.com.mydexdemo.dynamic.impl;  
import wangyang.zun.com.mydexdemo.dynamic.Dynamic;  
public class DynamicImpl implements Dynamic {  
  
    @Override  
    public String sayHello() {  
        return new StringBuilder(getClass().getName()).append(" is loaded by DexClassLoader").toString();  
    }  
} 

很簡(jiǎn)單輸出一句話,"DynamicImpl is loaded by DexClassLoader."
具體的工程目錄如下圖:


點(diǎn)擊Build -> make project比藻,這時(shí)候會(huì)在build\intermediates\classes\debug目錄下生成對(duì)應(yīng)的classes文件铝量。

好了我們要把DynamicImpl這個(gè)class轉(zhuǎn)換成Dalvik可識(shí)別的dex文件,分兩步:

1.先導(dǎo)出DynamicImpl這個(gè)類為jar包的形式银亲;
2.通過android sdk自帶的dx.jar工具轉(zhuǎn)換jar包為dex文件慢叨。

完成第一步,當(dāng)時(shí)遇到點(diǎn)麻煩务蝠,由于eclipse是基于ant并且有可視化工具拍谐,可以直接導(dǎo)出指定文件的jar包,但是android studio不行馏段,那怎么辦呢轩拨?
我們可以通過gradle task來(lái)打包,打開app目錄下的build.gradle文件院喜,切記不是根目錄的build.gradle文件气嫁,加上以下代碼:

//刪除dynamic.jar包任務(wù)  
task clearJar(type: Delete) {  
    delete 'libs/dynamic.jar'  
}  
  
//打包任務(wù)  
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {  
    //指定生成的jar名  
    baseName 'dynamic'  
    //從哪里打包c(diǎn)lass文件  
    from('build/intermediates/classes/debug/wangyang/zun/com/mydexdemo/dynamic/impl/')  
    //打包到j(luò)ar后的目錄結(jié)構(gòu)  
    into('wangyang/zun/com/mydexdemo/dynamic/impl/')  
    //去掉不需要打包的目錄和文件  
    exclude('test/', 'Dynamic.class', 'BuildConfig.class', 'R.class')  
    //去掉R$開頭的文件  
    exclude{ it.name.startsWith('R$');}  
}  
makeJar.dependsOn(clearJar, build)  

打開AS的 terminal窗口: cd app進(jìn)入app目錄,執(zhí)行gradle makeJar够坐,然后等待直到出現(xiàn)Build Successfully,這時(shí)會(huì)在build目錄下出現(xiàn)libs/dynamic.jar文件崖面,這個(gè)文件就是我們要用的jar包元咙,我們可以使用jd-gui打開看下是不是只有DynamicImpl這個(gè)class;

第二步巫员,使用sdk提供的dx.jar將導(dǎo)出的dynamic.jar轉(zhuǎn)換成Dalvik可識(shí)別的dex格式庶香,新版的sdk已經(jīng)將dx.jar放到build-tools\23.0.2\lib目錄下,我們?cè)赿os下或者在Android studio terminal下面進(jìn)入到此目錄简识,然后運(yùn)行下面的命令:

dx --dex --output=dynamic_dex.jar dynamic.jar

output是你的輸出目錄赶掖,默認(rèn)就是在當(dāng)前的根目錄下感猛,執(zhí)行完成后我們就在當(dāng)前目錄下生成了Davilk虛擬機(jī)可執(zhí)行的dex文件,因?yàn)檫@條命令同時(shí)會(huì)打包dex文件奢赂,因此后綴是jar陪白,我們用jd-gui打開dynamic.jar和dynamic_dex.jar這兩個(gè)文件,看下他們有的結(jié)構(gòu)膳灶。

可以看到咱士,打包后的文件其實(shí)是一個(gè)classes.dex文件,目前為止我們要做的工作已經(jīng)準(zhǔn)備就緒了轧钓,接下來(lái)就是要在demo中使用這個(gè)dex文件序厉。

二、刪除剛剛新建的impl包以及包內(nèi)的文件:
因?yàn)榈认挛覀円褂玫氖莇ex下面的Dynamic實(shí)現(xiàn)類毕箍,所以我們需要?jiǎng)h除當(dāng)前工程下的DynamicImpl文件和impl包弛房,避免運(yùn)行時(shí)出錯(cuò)。同時(shí)而柑,我們要把剛剛生成的dynamic_dex.jar文件放到assets目錄下文捶,等下需要把它c(diǎn)opy到app/data下使用,刪除后的整個(gè)工程目錄如下:

FileUtils類是從assets目錄下copy文件到app/data/cache目錄牺堰,源碼如下:

public class FileUtils {  
  
    public static void copyFiles(Context context, String fileName, File desFile) {  
        InputStream in = null;  
        OutputStream out = null;  
        try {  
            in = context.getApplicationContext().getAssets().open(fileName);  
            out = new FileOutputStream(desFile.getAbsolutePath());  
            byte[] bytes = new byte[1024];  
            int i;  
            while ((i = in.read(bytes)) != -1)  
                out.write(bytes, 0 , i);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }finally {  
            try {  
                if (in != null)  
                    in.close();  
                if (out != null)  
                    out.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
  
        }  
    }  
  
    public static boolean hasExternalStorage() {  
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);  
    }  
  
    /** 
     * 獲取緩存路徑 
     * 
     * @param context 
     * @return 返回緩存文件路徑 
     */  
    public static File getCacheDir(Context context) {  
        File cache;  
        if (hasExternalStorage()) {  
            cache = context.getExternalCacheDir();  
        } else {  
            cache = context.getCacheDir();  
        }  
        if (!cache.exists())  
            cache.mkdirs();  
        return cache;  
    }  
  
}  

打開MainActivity:

public class MainActivity extends AppCompatActivity {  
  
    private Dynamic dynamic;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        //添加一個(gè)點(diǎn)擊事件  
        findViewById(R.id.tx).setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                loadDexClass();  
            }  
        });  
    }  
  
    /** 
     * 加載dex文件中的class拄轻,并調(diào)用其中的sayHello方法 
     */  
    private void loadDexClass() {  
        File cacheFile = FileUtils.getCacheDir(getApplicationContext());  
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "dynamic_dex.jar";  
        File desFile = new File(internalPath);  
        try {  
            if (!desFile.exists()) {  
                desFile.createNewFile();  
                FileUtils.copyFiles(this, "dynamic_dex.jar", desFile);  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
  
        //下面開始加載dex class  
        DexClassLoader dexClassLoader = new DexClassLoader(internalPath, cacheFile.getAbsolutePath(), null, getClassLoader());  
        try {  
            Class libClazz = dexClassLoader.loadClass("wangyang.zun.com.mydexdemo.dynamic.impl.DynamicImpl");  
            dynamic = (Dynamic) libClazz.newInstance();  
            if (dynamic != null)  
                Toast.makeText(this, dynamic.sayHelloy(), Toast.LENGTH_LONG).show();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
}  

程序運(yùn)行的效果圖如下:


至此,我們關(guān)于Android Dex動(dòng)態(tài)加載機(jī)制的原理講到這里

Demo源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伟葫,一起剝皮案震驚了整個(gè)濱河市恨搓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筏养,老刑警劉巖斧抱,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異渐溶,居然都是意外死亡辉浦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門茎辐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宪郊,“玉大人,你說我怎么就攤上這事拖陆〕诨保” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵依啰,是天一觀的道長(zhǎng)乎串。 經(jīng)常有香客問我,道長(zhǎng)速警,這世上最難降的妖魔是什么叹誉? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任鸯两,我火速辦了婚禮,結(jié)果婚禮上长豁,老公的妹妹穿的比我還像新娘钧唐。我一直安慰自己,他們只是感情好蕉斜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布逾柿。 她就那樣靜靜地躺著,像睡著了一般宅此。 火紅的嫁衣襯著肌膚如雪机错。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天父腕,我揣著相機(jī)與錄音弱匪,去河邊找鬼。 笑死璧亮,一個(gè)胖子當(dāng)著我的面吹牛萧诫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枝嘶,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼帘饶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了群扶?” 一聲冷哼從身側(cè)響起及刻,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竞阐,沒想到半個(gè)月后缴饭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颗搂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年丢氢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卖丸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盏道。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猜嘱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朗伶,到底是詐尸還是另有隱情论皆,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布感凤,位于F島的核電站陪竿,受9級(jí)特大地震影響屠橄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜礁哄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一桐绒、第九天 我趴在偏房一處隱蔽的房頂上張望刻盐。 院中可真熱鬧,春花似錦馒疹、人聲如沸颖变。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至垫卤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歇盼,已是汗流浹背评抚。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工邢笙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鱼响,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓筐骇,卻偏偏與公主長(zhǎng)得像铛纬,于是被迫代替她去往敵國(guó)和親唬滑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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