前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android插件化学习之路(三)之调用外部.dex文件中的代码

Android插件化学习之路(三)之调用外部.dex文件中的代码

作者头像
老马的编程之旅
发布2022-06-22 10:08:23
1.1K0
发布2022-06-22 10:08:23
举报
文章被收录于专栏:深入理解Android

Java程序中,JVM虚拟机是通过类加载器ClassLoader加载.jar文件里面的类的。Android也类似,不过Android用的是Dalvik/ART虚拟机,不是JVM,也不能直接加载.jar文件,而是加载dex文件。

先要通过Android SDK提供的DX工具把.jar文件优化成.dex文件,然后Android的虚拟机才能加载。注意,有的Android应用能直接加载.jar文件,那是因为这个.jar文件已经经过优化,只不过后缀名没改(其实已经是.dex文件)。

.jar文件优化成.dex文件

首先我们可以通过JDK的编译命令javac把Java代码编译成.class文件,再使用jar命令把.class文件封装成.jar文件,这与编译普通Java程序的时候完全一样。 之后再用Android SDK的DX工具把.jar文件优化成.dex文件(在“android-sdk\build-tools\具体版本\”路径下)

代码语言:javascript
复制
dx --dex --output=target.dex origin.jar // target.dex就是我们要的了

此外,我们可以现把代码编译成APK文件,再把APK里面的.dex文件解压出来,或者直接把APK文件当成.dex使用(只是APK里面的静态资源文件我们暂时还用不到)。至此我们发现,无论加载.jar,还是.apk,其实都和加载.dex是等价的,Android能加载.jar和.apk,是因为它们都包含有.dex,直接加载.apk文件时,ClassLoader也会自动把.apk里的.dex解压出来。

**

加载并调用.dex里面的方法

** 与JVM不同,Android的虚拟机不能用ClassCload直接加载.dex,而是要用DexClassLoader或者PathClassLoader,他们都是ClassLoader的子类,这两者的区别是 1) DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk; 2) PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件; 使用前,先看看DexClassLoader的构造方法

代码语言:javascript
复制
 public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
    super((String) null, (File) null, (String) null, (ClassLoader) null);
    throw new RuntimeException("Stub!");
}

注意,我们之前提到的,DexClassLoader并不能直接加载外部存储的.dex文件,而是要先拷贝到内部存储里。这里的dexPath就是.dex的外部存储路径,而optimizedDirectory则是内部路径,libraryPath用null即可,parent则是要传入当前应用的ClassLoader,这与ClassLoader的“双亲代理模式”有关。

代码语言:javascript
复制
File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test_dexloader.jar");// 外部路径
File dexOutputDir = this.getDir("dex", 0);// 无法直接从外部路径加载.dex文件,需要指定APP内部路径作为缓存目录(.dex文件会被解压到此目录)
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null, getClassLoader());

到这里,我们已经成功把.dex文件给加载进来了,接下来就是如何调用.dex里面的代码,主要有两种方式。

如何调用.dex里面的代码

使用反射的方式

使用DexClassLoader加载进来的类,我们本地并没有这些类的源码,所以无法直接调用,不过可以通过反射的方法调用,简单粗暴。

代码语言:javascript
复制
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null, getClassLoader());
Class libProviderClazz = null;
try{
    libProviderClazz=dexClassLoader.loadClass("com.dexclassloader.MyLoader");
    // 遍历类里所有方法
    Method[]methods=libProviderClazz.getDeclaredMethods();
    for(int i=0;i<methods.length;i++){
    Log.e(TAG,methods[i].toString());
    }
    Method start=libProviderClazz.getDeclaredMethod("func");// 获取方法
    start.setAccessible(true);// 把方法设为public,让外部可以调用
    String string=(String)start.invoke(libProviderClazz.newInstance());// 调用方法并获取返回值
    Toast.makeText(this,string,Toast.LENGTH_LONG).show();
    }catch(Exception exception){
    // Handle exception gracefully here.
    exception.printStackTrace();
    }

使用接口的方式 毕竟.dex文件也是我们自己维护的,所以可以把方法抽象成公共接口,把这些接口也复制到主项目里面去,就可以通过这些接口调用动态加载得到的实例的方法了。

代码语言:javascript
复制
pulic interface IFunc{
    public String func();
}

// 调用
IFunc ifunc = (IFunc)libProviderClazz;
String string = ifunc.func();
Toast.makeText(this, string, Toast.LENGTH_LONG).show();

到这里,我们已经成功从外部路径动态加载一个.dex文件,并执行里面的代码逻辑了。通过从服务器下载最新的.dex文件并替换本地的旧文件,就能初步实现“APP的动态升级了”。

虽然我们已经能调用插件的方法了,但是还有如下问题

  1. 无法使用res目录下的资源,特别是使用XML布局,以及无法通过res资源到达自适应
  2. 无法动态加载新的Activity等组件,因为这些组件需要在Manifest中注册,动态加载无法更改当前APK的Manifest

这些问题的解决方案,将在以后的博客进行说明。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • .jar文件优化成.dex文件
  • 加载并调用.dex里面的方法
  • 如何调用.dex里面的代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档