前言

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文件路径,如果是外存路径,一定要加上读外存文件的权限( ),否则会报与上面一样的错误,这点参考文章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技术初探

更多相关文章

  1. Appium:通过wifi连接Android设备
  2. 关于Android(安卓)studio中httpclient不能用的问题
  3. android如何实现文件按时间先后顺序排列显示
  4. Android(安卓)使用SoundPool播放音频
  5. Android的编译过程 & Android(安卓)dex 方法限制的一些总结
  6. Android(安卓)Studio 配置SVN,通过Share project提交项目和实现忽
  7. Android屏幕适配小技巧swdp
  8. 开源项目之Android(安卓)Afinal框架
  9. 关于Android(安卓)添加系统级(java)服务和调用的编写实现说明

随机推荐

  1. Python 学习笔记二
  2. python 报错——Python TypeError: 'modu
  3. python获取外网IP并发邮件
  4. 在Python中,如何在使用WPF的程序中自动控
  5. CentOS下实现Flask + Virtualenv + uWSGI
  6. PySide-QtWebKit: CSS font-family没有效
  7. python学习笔记10(函数一): 函数使用、调用
  8. python爬取csdn的博客内容
  9. 利用python Pandas进行数据预处理
  10. python 带正则的search 模块