什么是類加載器?
類加載器(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è)類:DexClassLoader
和PathClassLoader
;而這兩個(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ī)制的原理講到這里