Android动态加载Dex机制解析
2016-04-12 12:45
633 查看
1.什么是类加载器?
类加载器(class loader)是 Java™中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例,需要了解ClassLoader可以参考这篇文章深入ClasssLoader
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工作机制大家可以参考这里:https://segmentfault.com/a/1190000004062880。
准备工作开始
一、打开Android studio 新建工程:工程目录是这样的:
动态加载进来的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(); } }很简单输出一句话,"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/') //打包到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实现类,所以我们需要删除当前工程下的IDynamic.java文件和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.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
相关文章推荐
- 在android中进行视频的分割
- React-Native系列Android——Native与Javascript通信原理(一)
- android 解析、生成二维码
- android Activity之间数据传递 Bitmap
- android 音频视频合并
- android 测试开发概述
- Android中的线程池和AsyncTask异步任务(二)
- Android 知识点积累
- 【转】Android总结之drawable(hdpi,mdpi,ldpi)文件夹的使用
- Android动画——布局联动
- Android Studio勾选后实现自动导包,和自动删除无用的导包
- Android蓝牙开发(三)
- 在Android 5.0中使用JobScheduler
- Android代码优化小技巧总结
- Android蓝牙开发(二)
- Android NDK——Log
- Android代码内存优化建议-Android资源篇
- android 6.0运行时新权限的申请(转载)
- 五步搞定Android开发环境部署——非常详细的Android开发环境搭建教程
- android gradle aar依赖, 修改默认apk名称