博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android动态载入Dex机制解析
阅读量:6158 次
发布时间:2019-06-21

本文共 6501 字,大约阅读时间需要 21 分钟。

1.什么是类载入器?

   类载入器(class loader)是 Java™中的一个非常重要的概念。类载入器负责载入 Java 类的字节代码到 Java 虚拟机中。

Java 虚拟机使用 Java 类的方式例如以下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类载入器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每一个这种实例用来表示一个 Java 类。通过此实例的 newInstance()方法就能够创建出该类的一个对象。实际的情况可能更加复杂。比方 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上全部的类载入器都是 java.lang.ClassLoader类的一个实例,须要了解ClassLoader能够參考这篇文章

2.Dalvik虚拟机类载入机制

Dalvik虚拟机如同其它Java虚拟机一样。在执行程序时首先须要将相应的类载入到内存中。

而在Java标准的虚拟机中。类载入能够从class文件里读取。也能够是其它形式的二进制流,因此。我们经常利用这一点,在程序执行时手动载入Class,从而达到代码动态载入执行的目的,可是Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类载入机制上。它们有同样的地方。也有不同之处。

我们先看下以下这张关于Android Classload机制的图。

与JVM不同,Dalvik的虚拟机不能用ClassCload直接载入.dex,Android从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader;而这两个类就是我们载入dex文件的关键,这两者的差别是:

1.DexClassLoader:能够载入jar/apk/dex。能够从SD卡中载入安装的apk;

2.PathClassLoader:要传入系统中apk的存放Path,所以仅仅能载入已经安装的apk文件。

关于Android 动态载入基础 ClassLoader工作机制大家能够參考这里:。

准备工作開始

一、打开Android studio 新建project:
project文件夹是这种:
动态载入进来的class怎样使用,一般有2种办法,一种是使用反射调用。这样的我不多做介绍;另一种是使用接口编程的方式来调用相应的方法,
毕竟.dex文件也是我们自己维护的。所以能够把方法抽象成公共接口,把这些接口也拷贝到主项目里面去,就能够通过这些接口调用动态载入得到的实例的方法了。
接下来我们源代码包以下新建一个包名称是dynamic,然后在dynamic下新建一个interface接口Dynamic。里面有个接口方法,就叫sayHello()吧,返回一个String,到时候我们能够通过Toast弹出来,Dynamic.java:
package wangyang.zun.com.mydexdemo.dynamic;/** * Created by WangYang on 2016/3/11. */public interface Dynamic {    String sayHello();}
接着我们新建一个impl包。并实现Dynamic接口,DynamicImpl.java:
package wangyang.zun.com.mydexdemo.dynamic.impl;import wangyang.zun.com.mydexdemo.dynamic.Dynamic;/** * Created by WangYang on 2016/3/11. */public class DynamicImpl implements Dynamic {    @Override    public String sayHello() {        return new StringBuilder(getClass().getName()).append(" is loaded by DexClassLoader").toString();    }}
非常easy输出一句话,"DynamicImpl is loaded by DexClassLoader."
详细的project文件夹例如以下图:
点击Build -> make project。这时候会在build\intermediates\classes\debug文件夹下生成相应的classes文件。

好了我们要把DynamicImpl这个class转换成Dalvik可识别的dex文件,分两步:
1.先导出DynamicImpl这个类为jar包的形式。
2.通过android sdk自带的dx.jar工具转换jar包为dex文件。

完毕第一步。当时遇到点麻烦。因为eclipse是基于ant而且有可视化工具,能够直接导出指定文件的jar包。可是android studio不行,那怎么办呢?
我们能够通过gradle task来打包,打开app文件夹下的build.gradle文件,切记不是根文件夹的build.gradle文件,加上下面代码:
//删除dynamic.jar包任务task clearJar(type: Delete) {    delete 'libs/dynamic.jar'}//打包任务task makeJar(type:org.gradle.api.tasks.bundling.Jar) {    //指定生成的jar名    baseName 'dynamic'    //从哪里打包class文件    from('build/intermediates/classes/debug/wangyang/zun/com/mydexdemo/dynamic/')    //打包到jar后的文件夹结构    into('wangyang/zun/com/mydexdemo/dynamic/')    //去掉不须要打包的文件夹和文件    exclude('test/', 'Dynamic.class', 'BuildConfig.class', 'R.class')    //去掉R$开头的文件    exclude{ it.name.startsWith('R$');}}makeJar.dependsOn(clearJar, build)
打开AS的 terminal窗体: cd app进入app文件夹,运行gradle makeJar,然后等待直到出现Build Successfully,这时会在build文件夹下出现libs/dynamic.jar文件。这个文件就是我们要用的jar包,我们能够使用jd-gui打开看下是不是仅仅有DynamicImpl这个class。
第二步,使用sdk提供的dx.jar将导出的dynamic.jar转换成Dalvik可识别的dex格式。新版的sdk已经将dx.jar放到build-tools\23.0.2\lib文件夹下。我们在dos下或者在Android studio terminal以下进入到此文件夹,然后执行以下的命令:
dx --dex --output=dynamic_dex.jar dynamic.jar
output是你的输出文件夹,默认就是在当前的根文件夹下,运行完毕后我们就在当前文件夹下生成了Davilk虚拟机可运行的dex文件。由于这条命令同一时候会打包dex文件,因此后缀是jar。我们用jd-gui打开dynamic.jar和dynamic_dex.jar这两个文件,看下他们有的结构。
能够看到,打包后的文件事实上是一个classes.dex文件,眼下为止我们要做的工作已经准备就绪了,接下来就是要在demo中使用这个dex文件。

二、删除刚刚新建的impl包以及包内的文件:
由于等下我们要使用的是dex以下的IDynamic实现类。所以我们须要删除当前project下的IDynamic.java文件和impl包,避免执行时出错。

同一时候,我们要把刚刚生成的dynamic_dex.jar文件放到assets文件夹下,等下须要把它copy到app/data下使用。删除后的整个project文件夹例如以下:

FileUtils类是从assets文件夹下copy文件到app/data/cache文件夹。源代码例如以下:
public class FileUtils {    public static void copyFiles(Context context, String fileName, File desFile) {        InputStream in = null;        OutputStream out = null;        try {            in = context.getApplicationContext().getAssets().open(fileName);            out = new FileOutputStream(desFile.getAbsolutePath());            byte[] bytes = new byte[1024];            int i;            while ((i = in.read(bytes)) != -1)                out.write(bytes, 0 , i);        } catch (IOException e) {            e.printStackTrace();        }finally {            try {                if (in != null)                    in.close();                if (out != null)                    out.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }    public static boolean hasExternalStorage() {        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);    }    /**     * 获取缓存路径     *     * @param context     * @return 返回缓存文件路径     */    public static File getCacheDir(Context context) {        File cache;        if (hasExternalStorage()) {            cache = context.getExternalCacheDir();        } else {            cache = context.getCacheDir();        }        if (!cache.exists())            cache.mkdirs();        return cache;    }}
打开MainActivity:
public class MainActivity extends AppCompatActivity {    private Dynamic dynamic;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //加入一个点击事件        findViewById(R.id.tx).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                loadDexClass();            }        });    }    /**     * 载入dex文件里的class,并调用当中的sayHello方法     */    private void loadDexClass() {        File cacheFile = FileUtils.getCacheDir(getApplicationContext());        String internalPath = cacheFile.getAbsolutePath() + File.separator + "dynamic_dex.jar";        File desFile = new File(internalPath);        try {            if (!desFile.exists()) {                desFile.createNewFile();                FileUtils.copyFiles(this, "dynamic_dex.jar", desFile);            }        } catch (IOException e) {            e.printStackTrace();        }        //以下開始载入dex class        DexClassLoader dexClassLoader = new DexClassLoader(internalPath, cacheFile.getAbsolutePath(), null, getClassLoader());        try {            Class libClazz = dexClassLoader.loadClass("wangyang.zun.com.mydexdemo.dynamic.impl.IDynamic");            dynamic = (Dynamic) libClazz.newInstance();            if (dynamic != null)                Toast.makeText(this, dynamic.sayHelloy(), Toast.LENGTH_LONG).show();        } catch (Exception e) {            e.printStackTrace();        }    }}
程序执行的效果图例如以下:
至此,我们关于Android Dex动态载入机制的原理说到这里。接下来我会分析下通过Dex实现热修复的基本原理。
Demo源代码地址https://github.com/wy353208214/MyDexDemo
你可能感兴趣的文章
MyBatis启动:MapperStatement创建
查看>>
调查问卷相关
查看>>
eclipse启动无响应,老是加载不了revert resources,或停留在Loading workbench状态
查看>>
1. Git-2.12.0-64-bit .exe下载
查看>>
怎样关闭“粘滞键”?
查看>>
[转]React 教程
查看>>
拓扑排序介绍
查看>>
eclipse打开工作空间(workspace)没有任务反应
查看>>
使用Sybmol模块来构建神经网络
查看>>
字符串去分割符号
查看>>
WPF中,多key值绑定问题,一个key绑定一个界面上的对象
查看>>
UML类图简明教程
查看>>
java反编译工具(Java Decompiler)
查看>>
Android开发之自定义对话框
查看>>
微信Access Token 缓存方法
查看>>
Eclipsed的SVN插件不能识别之前工作空间的项目
查看>>
Linux 查看iptables状态-重启
查看>>
amazeui学习笔记一(开始使用2)--布局示例layouts
查看>>
c#中lock的使用(用于预约超出限额的流程)
查看>>
ODI基于源表时间戳字段获取增量数据
查看>>