Android之Dex动态加载机制解析

1.什么是类加载器?

类加载器(Class Loader)是Java中一个很重要的概念,类加载器负责加载Java类的字节码代码到Java虚拟机中。

Java虚拟机使用Java类的方式如下:Java源代码(.java文件)在经过Java编译器编译之后就被转为Java字节码代码(.class文件)。类加载器负责读取Java字节码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个Java类。通过此实例的newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如Java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

基本上所有类加载器都是java.lang.ClassLoader类的一个实例,需要了解ClassLoader可以参考这篇文章深入浅出ClassLoader;

2.Dalvik虚拟机类记载机制?

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

我们先看下下面这张关于Android ClassLoader机制图:

Android之Dex动态加载机制解析_第1张图片

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

1.DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;

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

关于Android动态加载基础ClassLoader工作机制大家可以参考这里:

https://segmentfault.com/a/1190000004062880

3.Dex动态加载流程

3.1准备工作开始

1)打开Android Studio新建工程:

Android之Dex动态加载机制解析_第2张图片

 工程目录是这样的:

Android之Dex动态加载机制解析_第3张图片

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

package com.gome.childrenmanager.dynamic;

public interface Dynamic {
    String sayHello();
}

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

package com.gome.childrenmanager.dynamic.impl;

import com.gome.childrenmanager.dynamic.Dynamic;

public class DynamicImpl implements Dynamic {

    @Override
    public String sayHello() {
        return new StringBuilder(getClass().getSimpleName()).append(" is loaded by DexClassLoader.").toString();
    }
}

 很简单输出一句话,"DynamicImpl is loaded by DexClassLoader."

具体的工程目录如下图:

Android之Dex动态加载机制解析_第4张图片

点击Build->make project,这时候会在build\intermediates\javac\debug\classes(目录不同版本可能有一些差异)目录下生成对应的classes文件;

 Android之Dex动态加载机制解析_第5张图片

 好了我们要把DynamicImpl这个class转换成Davlik可识别的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.jvm.tasks.Jar){
    //指定生成jar名
    baseName('dynamic')
    //从哪里打包class文件
    from('build\\intermediates\\javac\\debug\\classes\\com\\gome\\childrenmanager\\dynamic\\')
    //打包到jar后目录接口
    into('com\\gome\\childrenmanager\\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
Android之Dex动态加载机制解析_第6张图片

output是你的输出目录,默认就是在当前的根目录下,执行完成后我们就在当前的Dalvik虚拟机下可执行的dex文件,因为这条命令同时会打包dex文件,因此后缀是jar,我们用jd-gui打开dynamic.jar和dynamic_dex.jar这两个文件,看下他们有的结构。

Android之Dex动态加载机制解析_第7张图片

Android之Dex动态加载机制解析_第8张图片

 可以看到,打包后的文件其实是一个classes.dex文件,目前为止我们要做的工作已经准备就绪了,接下来就是要在demo中使用这个dex文件。

3.2删除刚刚新建的impl包以及包内的文件:

因为等下我们要使用的是dex下面的Dynamic实现类,所以我们需要删除当前工程下的DynamicImpl.java文件和impl包,避免运行时出错。同时,我们要把刚刚生成的dynamic_dex.jar文件放到assets目录下,等下需要把它copy到app/data下使用,删除后的整个工程目录如下:

Android之Dex动态加载机制解析_第9张图片

 

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 len;
            while ((len = in.read(bytes)) != -1){
                out.write(bytes,0,len);
            }
        } 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.dynamic).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("com.gome.childrenmanager.dynamic.impl.DynamicImpl");
            dynamic = (Dynamic) libClazz.newInstance();
            if(dynamic != null){
                Toast.makeText(this, dynamic.sayHello(), Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

程序运行的效果图如下:

Android之Dex动态加载机制解析_第10张图片

 至此,我们关于Android Dex动态加载机制的原理讲到这里,接下来我会分析下通过Dex实现热修复的基本原理。

参考:

https://blog.csdn.net/wy353208214/article/details/50859422

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android混淆整理,android,java,dex加载)