Android動(dòng)態(tài)加載dex入門

前言

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)開(kāi)始

新建一個(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),我們需要編寫(xiě)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

生成jar包的方法
執(zhí)行完畢后在這個(gè)目錄下會(huì)生成對(duì)應(yīng)的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.

執(zhí)行結(jié)果

4.使用adb命令adb push out.jar sdcard/out.jar將out.jar放到SD卡下

上傳過(guò)程

5.編寫(xiě)客戶端調(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存放位置,此位置一定要是可讀寫(xiě)且僅該應(yīng)用可讀寫(xiě)(安全性考慮),所以只能放在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é)束

調(diào)用成功截圖

參考博客:Android動(dòng)態(tài)加載dex技術(shù)初探

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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