你對Android
中的ClassLoader
了解嗎湖饱?
在回答這個問題之前蜘矢,我們需要知道Android
中ClassLoader
的類型揍障。通過IDEA
的類的繼承結(jié)構(gòu)示意圖可以看到溉旋。
可以看到有很多類型的ClassLoader
,我們可以嘗試著看看煮仇,平常我們使用的都是哪些ClassLoader
呢劳跃?我們隨便運(yùn)行一個空的項(xiàng)目然后斷點(diǎn)看下
從斷點(diǎn)中,我們可以知道
- 加載
MainActivity
類的ClassLoader
是PathClassLoader
浙垫。 -
PathClassLoader
的父親是BootClassLoader
PathClassLoader
通過上述的類的繼承結(jié)構(gòu)圖可以知道刨仑,PathClassLoader
屬于BaseDexClassLoader
的子類郑诺。在Anroid
中,PathClassLoader
通常用來加載已經(jīng)安裝的apk的dex文件(安裝的apk的dex文件會存儲在/data/dalvik-cache中)
BootClassLoader
Android系統(tǒng)啟動時(shí)會使用BootClassLoader
來預(yù)加載常用類杉武。
DexClassLoader
除了上述介紹的兩個類型辙诞,還有DexClassLoader
也經(jīng)常被使用到。因?yàn)樗梢愿鶕?jù)路徑加載dex文件以及包含dex的壓縮文件(apk和jar文件)轻抱。這樣就為動態(tài)加載提供了可能性飞涂。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
我們可以知道DexClassLoader
需要四個參數(shù)
- 要加載類文件的路徑
- 優(yōu)化
dex
文件的目錄,不能為空 - 包含
C/C++
庫的路徑集合,多個路徑用文件分隔符分隔分割祈搜,可以為空 - 父加載器
動態(tài)加載代碼
上面介紹了很多概念性的東西较店。接下來要實(shí)戰(zhàn)一下。動態(tài)加載我們SD
中的jar
文件容燕。然后調(diào)用方法梁呈。首先我們需要一個被加載的jar
文件。先編譯一個測試類,這個類很簡單缰趋,只是返回一串字符串
public class HelloWorld {
public HelloWorld() {
}
public static final String getMessage() {
return "hello world";
}
}
然后找到這個類的字節(jié)碼
,它所在的目錄如圖
使用命令行捧杉,編譯該字節(jié)碼
為jar
文件(因?yàn)槭俏募A的關(guān)系,中間需要創(chuàng)建一個MANIFEST.MF
文件)然后使用命令將class
文件編譯成jar
文件
jar cvf demo.jar HelloWorld.class
當(dāng)我們得到jar
文件后秘血,將jar
文件放到assest
文件夾中。嘗試著用自己的ClassLoader
來加載它评甜。
public class MainActivity extends AppCompatActivity implements PermissionUtils.SimpleCallback {
public static final String FINAL_PATH = SDCardUtils.getSDCardPathByEnvironment() + "/new/demo2.jar";
TextView tvHelloWorld;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvHelloWorld = findViewById(R.id.tvHelloWorld);
PermissionUtils.permission(PermissionConstants.STORAGE).callback(this).request();
}
@SuppressWarnings("all")
@Override
public void onGranted() {
boolean copyResult = ResourceUtils.copyFileFromAssets("demo2.jar", FINAL_PATH);
if (copyResult) {
File cc = getDir("dex", 0);
DexClassLoader classLoader = new DexClassLoader(FINAL_PATH, cc.getAbsolutePath(), null, getClassLoader());
try {
Class mm = classLoader.loadClass("cc.dd.mm.HelloWorld");
Method method = mm.getMethod("getMessage");
String message = (String) method.invoke(null);
tvHelloWorld.setText(message);
} catch (Exception e) {
System.out.println(e);
}
}
}
@Override
public void onDenied() {
}
}
可以看到灰粮,代碼的邏輯很簡單。就是檢查SD卡
的讀寫權(quán)限忍坷。然后將事先放在assets
文件夾中的jar
拷貝到SD
卡中粘舟。用自己的ClassLoader
來加載jar
文件。之后嘗試調(diào)用其中的方法佩研。
當(dāng)你嘗試的運(yùn)行APP
的時(shí)候柑肴,你會發(fā)現(xiàn)有一個這樣的錯誤
No original dex files found for dex location
這是因?yàn)槲覀冎爸苯訉?code>.class文件轉(zhuǎn)化成.jar
文件。但是Android Dalvik
并不能識別java
二進(jìn)制代碼旬薯。所以我們需要將剛剛生成的jar
文件晰骑,改成能被Android Dalvik
所識別的jar
文件。這里需要dx
工具绊序,幫我們完成這個任務(wù)硕舆。(這個工具在:安卓安裝目錄下\SDK\build-tools)
dx --dex --output=old.jar new.jar
這樣你就得到一個新的jar
文件,然后替換之前的jar
骤公。再次運(yùn)行抚官。就會得到你想要的結(jié)果了。