前言
Android構(gòu)建過(guò)程是將Java源代碼轉(zhuǎn)換成.dex(Dalvik EXexcutable)文件,這些文件是Android OS在Dalvik虛擬機(jī)("DVM")中運(yùn)行的文件质和。所以我們不能直接加載使用基于class的jar稳摄,而是需要將class轉(zhuǎn)化成dex字節(jié)碼。優(yōu)化后的字節(jié)碼可以存放在一個(gè).jar中饲宿,只要其內(nèi)部存放的是.dex即可使用秩命。
如何轉(zhuǎn)換呢?
在Android的SDK中為我們提供了一個(gè)dx命令(在\android-sdk\build-tools\version[23.0.1] 或 \android-sdk\platform-tools下能找到);命令使用方式為:dx --dex --output=out.jar in.jar褒傅,該命令將包含class的in.jar轉(zhuǎn)化為包含dex的out.jar文件。
Android支持的動(dòng)態(tài)加載
Android支持動(dòng)態(tài)加載的兩種方式是:DexClassLoader和PathClassLoader袄友。它倆的區(qū)別:
- DexClassLoader可以加載jar/apk/dex殿托,可以從SD卡中加載未安裝的apk
- PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過(guò)的apk
點(diǎn)擊查看源碼分析
實(shí)驗(yàn)開始
新建一個(gè)Android工程
1.新建一個(gè)DexRes類
public class DexRes {
public String getString() {
return "我是來(lái)自dex中的資源";
}
}
2.編譯一下,在對(duì)應(yīng)的工程目錄下會(huì)生成對(duì)應(yīng)的class文件(build/intermediates/classes/debug/com/maqiang/dexdemo/DexRes.class),我們需要編寫gradle腳本將這個(gè)class文件先轉(zhuǎn)換成jar,腳本代碼如下:
android{
.....
//刪除jar包
task deleOldJar(type: Delete){
delete 'build/libs/in.jar'
}
//生成jar包
task makeJar(type: org.gradle.api.tasks.bundling.Jar){
baseName 'in'
from('build/intermediates/classes/debug/com/maqiang/dexdemo/DexRes.class')
into('com/maqiang/dexdemo')
}
}
注意:from表示需要轉(zhuǎn)換的class文件的地址剧蚣,into表示轉(zhuǎn)換后對(duì)應(yīng)的文件目錄(一定要和class文件中的package對(duì)應(yīng)起來(lái))
然后在Android studio中的右側(cè)面板中的Gradle中執(zhí)行我們的makeJar,執(zhí)行完畢后在工程的build/libs下就會(huì)有一個(gè)in.jar
3.將jar轉(zhuǎn)換成含dex的jar
我們將這個(gè)jar包拷貝到dx命令(\android-sdk\build-tools\version或 \android-sdk\platform-tools)所在的目錄下支竹,我是拷貝到了platform-tools下面,然后執(zhí)行命令dx --dex --output=out.jar in.jar鸠按,將in.jar轉(zhuǎn)換成含dex的out.jar.
4.使用adb命令adb push out.jar sdcard/out.jar將out.jar放到SD卡下
5.編寫客戶端調(diào)用代碼
核心思想就是使用DexClassLoader去加載dex礼搁,然后通過(guò)反射調(diào)用我們之前定義的方法獲取相關(guān)資源.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 點(diǎn)擊事件
* @param view
*/
public void loadDex(View view) {
File dexOutputDir = getDir("dex1", 0);
String dexPath = Environment.getExternalStorageDirectory() + File.separator + "out.jar";
DexClassLoader loader =
new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, getClassLoader());
try {
Class clz = loader.loadClass("com.maqiang.dexdemo.DexRes");
Method dexRes = clz.getDeclaredMethod("getString");
Toast.makeText(this, (CharSequence) dexRes.invoke(clz.newInstance()), Toast.LENGTH_LONG)
.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
此處需要注意DexClassLoader的四個(gè)參數(shù):
參數(shù)1 dexPath:待加載的dex文件路徑,如果是外存路徑目尖,一定要加上讀外存文件的權(quán)限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> )馒吴,否則會(huì)報(bào)與上面一樣的錯(cuò)誤,這點(diǎn)參考文章2中說(shuō)這個(gè)權(quán)限可有可無(wú)是錯(cuò)誤的瑟曲。(更正下:Android4.4 KitKat及以后的版本需要此權(quán)限饮戳,之前的版本不需要權(quán)限)
-
參數(shù)2 optimizedDirectory:解壓后的dex存放位置,此位置一定要是可讀寫且僅該應(yīng)用可讀寫(安全性考慮)洞拨,所以只能放在data/data下扯罐。本文getDir("dex1", 0)會(huì)在/data/data/**package/下創(chuàng)建一個(gè)名叫”app_dex1“的文件夾,其內(nèi)存放的文件是自動(dòng)生成output.dex烦衣;如果不滿足條件歹河,Android會(huì)報(bào)的錯(cuò)誤為:
java.lang.IllegalArgumentException: optimizedDirectory not readable/writable: /storage/sdcard0 java.lang.IllegalArgumentException: Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.
參數(shù)3 libraryPath:指向包含本地庫(kù)(so)的文件夾路徑掩浙,可以設(shè)為null
參數(shù)4 parent:父級(jí)類加載器,一般可以通過(guò)Context.getClassLoader獲取到秸歧,也可以通過(guò)ClassLoader.getSystemClassLoader()取到厨姚。
如果出現(xiàn)以下錯(cuò)誤,請(qǐng)檢查jar中的文件目錄是否使用正確寥茫,在打包過(guò)程中是否正確將對(duì)應(yīng)的class的打包成功.
java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.ShowToastImpl" on path: DexPathList[[zip file "/storage/emulated/0/testtoast.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
.....
Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Lcom/example/testdextoast/IShowToast;
... 16 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.IShowToast" on path: DexPathList[[zip file "/storage/emulated/0/testtoast.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
... 21 more
Suppressed: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.IShowToast" on path: DexPathList[[zip file "/data/app/com.example.testshowtoastdex-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
... 22 more
Suppressed: java.lang.ClassNotFoundException: com.example.testdextoast.IShowToast
... 23 more
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
Suppressed: java.lang.ClassNotFoundException: Didn't find class "com.example.testdextoast.ShowToastImpl" on path: DexPathList[[zip file "/data/app/com.example.testshowtoastdex-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
... 15 more
Suppressed: java.lang.ClassNotFoundException: com.example.testdextoast.ShowToastImpl
... 16 more
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
6.實(shí)驗(yàn)結(jié)束