前言
Android構(gòu)建過程是將Java源代碼轉(zhuǎn)換成.dex(Dalvik EXexcutable)文件,這些文件是Android OS在Dalvik虛擬機("DVM")中運行的文件。所以我們不能直接加載使用基于class的jar,而是需要將class轉(zhuǎn)化成dex字節(jié)碼。優(yōu)化后的字節(jié)碼可以存放在一個.jar中,只要其內(nèi)部存放的是.dex即可使用。
如何轉(zhuǎn)換呢?
在Android的SDK中為我們提供了一個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支持的動態(tài)加載
Android支持動態(tài)加載的兩種方式是:DexClassLoader和PathClassLoader。它倆的區(qū)別:
- DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk
- PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過的apk
點擊查看源碼分析
實驗開始
新建一個Android工程
1.新建一個DexRes類
public class DexRes {
public String getString() {
return "我是來自dex中的資源";
}
}
2.編譯一下,在對應(yīng)的工程目錄下會生成對應(yīng)的class文件(build/intermediates/classes/debug/com/maqiang/dexdemo/DexRes.class),我們需要編寫gradle腳本將這個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)換后對應(yīng)的文件目錄(一定要和class文件中的package對應(yīng)起來)
然后在Android studio中的右側(cè)面板中的Gradle中執(zhí)行我們的makeJar,執(zhí)行完畢后在工程的build/libs下就會有一個in.jar


3.將jar轉(zhuǎn)換成含dex的jar
我們將這個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,然后通過反射調(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);
}
/**
* 點擊事件
* @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的四個參數(shù):
參數(shù)1 dexPath:待加載的dex文件路徑,如果是外存路徑,一定要加上讀外存文件的權(quán)限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> ),否則會報與上面一樣的錯誤,這點參考文章2中說這個權(quán)限可有可無是錯誤的。(更正下:Android4.4 KitKat及以后的版本需要此權(quán)限,之前的版本不需要權(quán)限)
-
參數(shù)2 optimizedDirectory:解壓后的dex存放位置,此位置一定要是可讀寫且僅該應(yīng)用可讀寫(安全性考慮),所以只能放在data/data下。本文getDir("dex1", 0)會在/data/data/**package/下創(chuàng)建一個名叫”app_dex1“的文件夾,其內(nèi)存放的文件是自動生成output.dex;如果不滿足條件,Android會報的錯誤為:
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:指向包含本地庫(so)的文件夾路徑,可以設(shè)為null
參數(shù)4 parent:父級類加載器,一般可以通過Context.getClassLoader獲取到,也可以通過ClassLoader.getSystemClassLoader()取到。
如果出現(xiàn)以下錯誤,請檢查jar中的文件目錄是否使用正確,在打包過程中是否正確將對應(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.實驗結(jié)束
