Android动态加载Dex机制解析

什么是类加载器?

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

Dalvik虚拟机类加载机制

Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流,因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的,但是Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,它们有相同的地方,也有不同之处。

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

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

1.DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;2.PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件。

关于Android 动态加载基础 ClassLoader工作机制大家可以参考这里:http://www.jianshu.com/p/56021ff40c9d

准备工作开始

一、打开Android studio 新建工程:


工程目录是这样的:


动态加载进来的class如何使用,一般有2种办法,一种是使用反射调用,这种我不多做介绍;还有一种是使用接口编程的方式来调用对应的方法,毕竟.dex文件也是我们自己维护的,所以可以把方法抽象成公共接口,把这些接口也复制到主项目里面去,就可以通过这些接口调用动态加载得到的实例的方法了。
接下来我们源码包下面新建一个包名称是dynamic,然后在dynamic下新建一个interface接口Dynamic,里面有个接口方法,就叫sayHello()吧,返回一个String,到时候我们可以通过Toast弹出来,Dynamic.java:

package wangyang.zun.com.mydexdemo.dynamic;  
public interface Dynamic {  
    String sayHello();  
}  

接着我们新建一个impl包,并实现Dynamic接口,DynamicImpl.java:

package wangyang.zun.com.mydexdemo.dynamic.impl;  
import wangyang.zun.com.mydexdemo.dynamic.Dynamic;  
public class DynamicImpl implements Dynamic {  
  
    @Override  
    public String sayHello() {  
        return new StringBuilder(getClass().getName()).append(" is loaded by DexClassLoader").toString();  
    }  
} 

很简单输出一句话,"DynamicImpl is loaded by DexClassLoader."
具体的工程目录如下图:


点击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/impl/')  
    //打包到jar后的目录结构  
    into('wangyang/zun/com/mydexdemo/dynamic/impl/')  
    //去掉不需要打包的目录和文件  
    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下面的Dynamic实现类,所以我们需要删除当前工程下的DynamicImpl文件和impl包,避免运行时出错。同时,我们要把刚刚生成的dynamic_dex.jar文件放到assets目录下,等下需要把它copy到app/data下使用,删除后的整个工程目录如下:

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.DynamicImpl");  
            dynamic = (Dynamic) libClazz.newInstance();  
            if (dynamic != null)  
                Toast.makeText(this, dynamic.sayHelloy(), Toast.LENGTH_LONG).show();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
}  

程序运行的效果图如下:


至此,我们关于Android Dex动态加载机制的原理讲到这里

Demo源码地址

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

推荐阅读更多精彩内容