Android动态加载dex入门

前言

Android构建过程是将Java源代码转换成.dex(Dalvik EXexcutable)文件,这些文件是Android OS在Dalvik虚拟机("DVM")中运行的文件。所以我们不能直接加载使用基于class的jar,而是需要将class转化成dex字节码。优化后的字节码可以存放在一个.jar中,只要其内部存放的是.dex即可使用。

如何转换呢?

在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转化为包含dex的out.jar文件。

Android支持的动态加载

Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader。它俩的区别:

  • DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk
  • PathClassLoader只能加载系统中已经安装过的apk
    点击查看源码分析

实验开始

新建一个Android工程
1.新建一个DexRes类

public class DexRes {
  public String getString() {
    return "我是来自dex中的资源";
  }
}

2.编译一下,在对应的工程目录下会生成对应的class文件(build/intermediates/classes/debug/com/maqiang/dexdemo/DexRes.class),我们需要编写gradle脚本将这个class文件先转换成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表示需要转换的class文件的地址,into表示转换后对应的文件目录(一定要和class文件中的package对应起来)

然后在Android studio中的右侧面板中的Gradle中执行我们的makeJar,执行完毕后在工程的build/libs下就会有一个in.jar

生成jar包的方法
执行完毕后在这个目录下会生成对应的jar

3.将jar转换成含dex的jar
我们将这个jar包拷贝到dx命令(\android-sdk\build-tools\version或 \android-sdk\platform-tools)所在的目录下,我是拷贝到了platform-tools下面,然后执行命令dx --dex --output=out.jar in.jar,将in.jar转换成含dex的out.jar.

执行结果

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

上传过程

5.编写客户端调用代码
核心思想就是使用DexClassLoader去加载dex,然后通过反射调用我们之前定义的方法获取相关资源.

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的四个参数:

  • 参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> ),否则会报与上面一样的错误,这点参考文章2中说这个权限可有可无是错误的。(更正下:Android4.4 KitKat及以后的版本需要此权限,之前的版本不需要权限)

  • 参数2 optimizedDirectory:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。本文getDir("dex1", 0)会在/data/data/**package/下创建一个名叫”app_dex1“的文件夹,其内存放的文件是自动生成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.
    
  • 参数3 libraryPath:指向包含本地库(so)的文件夹路径,可以设为null

  • 参数4 parent:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。

如果出现以下错误,请检查jar中的文件目录是否使用正确,在打包过程中是否正确将对应的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.实验结束

调用成功截图

参考博客:Android动态加载dex技术初探

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容