【Android高级】Dalvik虚拟机及其类加载器讲解

插件开发的过程中DexClassLoader和PathClassLoader这两个类加载器了是很重要的,但是他们也是有区别的,而且我们也知道PathClassLoader是Android应用中的默认加载器。他们的区别是:

DexClassLoader可以加载任何路径的apk/dex/jar

PathClassLoader只能加载/data/app中的apk,也就是已经安装到手机中的apk。这个也是PathClassLoader作为默认的类加载器的原因,因为一般程序都是安装了,在打开,这时候PathClassLoader就去加载指定的apk(解压成dex,然后在优化成odex)就可以了。

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

然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,它们有相同的地方,也有不同之处。我们必须区别对待

例如,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,大家就没必要走弯路了。参看源码我们知道,Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空

[cpp] view
plain
copy

  1. static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args,JValue* pResult){
  2. Object* loader = (Object*) args[0];
  3. StringObject* nameObj = (StringObject*) args[1];
  4. const u1* data = (const u1*) args[2];
  5. int offset = args[3];
  6. int len = args[4];
  7. Object* pd = (Object*) args[5];
  8. char* name = NULL;
  9. name = dvmCreateCstrFromString(nameObj);
  10. LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n",loader, name, data, offset, len, pd);
  11. dvmThrowException("Ljava/lang/UnsupportedOperationException;","can‘t load this type of class file");
  12. free(name);
  13. RETURN_VOID();
  14. }

Dalvik虚拟机类加载机制

那如果在Dalvik虚拟机里,ClassLoader不好使,我们如何实现动态加载类呢?Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。其中需要特别说明的是PathClassLoader中一段被注释掉的代码:

[java] view
plain
copy

  1. /* --this doesn‘t work in current version of Dalvik--
  2. if (data != null) {
  3. System.out.println("--- Found class " + name
  4. + " in zip[" + i + "] ‘" + mZips[i].getName() + "‘");
  5. int dotIndex = name.lastIndexOf(‘.‘);
  6. if (dotIndex != -1) {
  7. String packageName = name.substring(0, dotIndex);
  8. synchronized (this) {
  9. Package packageObj = getPackage(packageName);
  10. if (packageObj == null) {
  11. definePackage(packageName, null, null,
  12. null, null, null, null, null);
  13. }
  14. }
  15. }
  16. return defineClass(name, data, 0, data.length);
  17. }
  18. */

这从另一方面佐证了defineClass函数在Dalvik虚拟机里确实是被阉割了。而在这两个继承自ClassLoader的类加载器,本质上是重载了ClassLoader的findClass方法。在执行loadClass时,我们可以参看ClassLoader部分源码:

[java] view
plain
copy

  1. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
  2. Class<?> clazz = findLoadedClass(className);
  3. if (clazz == null) {
  4. try {
  5. clazz = parent.loadClass(className, false);
  6. } catch (ClassNotFoundException e) {
  7. // Don‘t want to see this.
  8. }
  9. if (clazz == null) {
  10. clazz = findClass(className);
  11. }
  12. }
  13. return clazz;
  14. }

因此DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass方法)。也就是说,它们在加载一个类之前,回去检查自己以及自己以上的类加载器是否已经加载了这个类。如果已经加载过了,就会直接将之返回,而不会重复加载。

DexClassLoader和PathClassLoader其实都是通过DexFile这个类来实现类加载的。这里需要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。因此,我们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。

也许有人想到,既然DexFile可以直接加载类,那么我们为什么还要使用ClassLoader的子类呢?DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要将包含包名的类名中的”.”转换为”/”我们看一下loadClass代码就清楚了:

[java] view
plain
copy

  1. public Class loadClass(String name, ClassLoader loader) {
  2. String slashName = name.replace(‘.‘, ‘/‘);
  3. return loadClassBinaryName(slashName, loader);
  4. }

在这段代码前有一段注释,截取关键一部分就是说:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 这就是我们需要使用ClassLoader子类的原因。至于它是如何验证是否是在ClassLoader中调用此方法的,我没有研究,大家如果有兴趣可以继续深入下去。

有一个细节,可能大家不容易注意到。PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-08 09:58:03

【Android高级】Dalvik虚拟机及其类加载器讲解的相关文章

Android的Dalvik虚拟机

一. Dalvik虚拟机 其实也算是一个JAVA虚拟机,只不过它执行的不是class文件,而是dex文件 Dalvik虚拟机和ART虚拟机都实现了三个用来抽象Java虚拟机的接口: 1. JNI_GetDefaultJavaVMInitArgs -- 获取虚拟机的默认初始化参数 2. JNI_CreateJavaVM -- 在进程中创建虚拟机实例 3. JNI_GetCreatedJavaVMs -- 获取进程中创建的虚拟机实例 在Android系统中,Davik虚拟机实现在libdvm.so中

深入JAVA虚拟机之类加载器

前言:虚拟机设计团队把类加载阶段中"通过一个类的权限定名来获取描述此类的二进制字节流"这个动作放到虚拟机外部区实现,让程序自己决定如何去获取所需的类.实现这个动作的代码模块就被称为类加载器. 它最初是为了满足Java Applet的需求而被开发,而现在Java Applet基本已经宣布死亡,但类加载器却在类层次划分.OSGi.热部署.代码加密等领域大放异彩.它是java技术体系中一块重要的基石. 对于任意一个类,都需要由加载该类的加载器和这个类本身一同确立在虚拟机中的唯一性.通俗说就是

【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载

类加载器的命名空间 每个类加载器又有一个命名空间,由其以及其父加载器组成 类加载器的命名空间的作用和影响 每个类加载器又有一个命名空间,由其以及其父加载器组成 在每个类加载器自己的命名空间中不能出现相同类名的类 (此处值得是类的全名,包含包名) 在不同的类命名空间中,可能会出现多个相同的类名的类 如下面的代码示例中, 首先定义一个类加载器 MyClassLoader static class MyClassLoader extends ClassLoader { private String c

传统的Java虚拟机和Android的Dalvik虚拟机及其ART模式

Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈.因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的. 基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些:而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的优点是执行速度快,主要的缺点是可移植性差.

Java类加载器及Android类加载器基础

引子 Android插件化与热更新技术日渐成熟,当你研究这些技术时会发现类加载器在其中占据重要地位.Java语言天生就有灵活性.动态性,支持运行期间动态组装程序,而这一切的基础就是类加载器. Java中的类加载器 Java灵活性和动态性的原因 Java源代码被编译器编译成字节码,即从.java文件编译为.class文件,而.class文件就是通过类加载器加载到虚拟机内存中的. 虚拟机的类加载(Class Loading)过程分为加载.链接(验证.准备.解析).初始化.使用.卸载等过程.这里仅考虑

Dalvik虚拟机进程和线程的创建过程分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8923484 我们知道,在Android系统中,Dalvik虚拟机是运行Linux内核之上的.如果我们把Dalvik虚拟机看作是一台机器,那么它也有进程 和线程的概念.事实上,我们的确是可以在Java代码中创建进程和线程,也就是Dalvik虚拟机进程和线程.那么,这些Dalvik虚拟机所创建的进程 和线程与其宿主Linux内核的进程和线程有什么关

Dalvik虚拟机的运行过程分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8914953 在前面一篇文章中,我们分析了Dalvik虚拟机在Zygote进程中的启动过程.Dalvik虚拟机启动完成之后,也就是在各个子模块初始化完成以及加 载了相应的Java核心类库之后,就是可以执行Java代码了.当然,Dalvik虚拟机除了可以执行Java代码之外,还可以执行Native代码,也 就是C和C++代码.在本文中,我们就将继续

类加载器深入理解和双亲委托模型的案例分析

类加载器深入理解和双亲委托模型的案例分析 我们知道类必须通过类加载器加载后,我们程序才可以使用.接下来我们就对类加载器进行分析,Java虚拟机的类加载器是如何加载类的.首先我们可以从ClassLoader的源码分析入手. ClassLoader 的源码分析 ClassLoader 的javadoc文档 javadoc文档是最权威的官方讲解,可以对ClassLoader有一个比较全面且正确的一个认知.下面是javadoc内容. A class loader is an object that is

Tomcat内核之类加载器工厂

Java虚拟机利用类加载器将类载入内存,以供使用.在此过程中类加载器要做很多的事情,例如读取字节数组.验证.解析.初始化等.而Java提供的URLClassLoader类能方便地将jar.class或网络资源加载到内存.Tomcat中则用一个工厂类ClassLoaderFactory把创建类加载器的细节进行封装,通过它可以很方便地创建自定义的类加载器. 如上图,利用createClassLoader方法并传入资源路径跟父类加载器即可创建一个自定义类加载器,此类加载器负责加载传入的所有资源. Cl