Android动态加载技术基础2之类加载(ClassLoader)

一、虚拟机类加载机制

  • 类加载过程是指虚拟机将描述类的数据从Class文件中加载到内存,并对数据进行校验,转化解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程。
  • 在Java中,类的加载和连接过程都是在程序运行期间完成。虽然会增加运行时的性能开销,但可以提高程序灵活性,这也是Java能够实现动态加载的原因之一。

1、类加载的过程

Java虚拟机四:类加载机制

二、Java中的ClassLoader

类加载阶段中,实现“通过一个类的全限定名获取定义类的二进制字节流”的动作被放在了虚拟机外部去实现,以便应用程序决定如何去加载所需要的类,实现这个动作的代码模块被称为“类加载器”
从Java虚拟机的角度上讲,只存在两种不同的类加载器:

  • Bootstrap ClassLoader:使用C++实现,是虚拟机的一部分。它主要负责加载存放在%JAVAHOME%/lib目录中的,或者被-Xbootclasspath指定的类库到虚拟机内存中,Bootstrap ClassLoader无法被java程序直接引用。
  • 继承自java.lang.ClassLoader的类加载器:
    • Extension ClassLoader:主要负责加载%JAVAHOME%/lib/ext目录中的,或者被java.ext.dirs系统变量指定路径的所有类。
    • Application ClassLoader:也被称为系统类加载器(因为其实getSystemClassLoader的返回对象),主要负责加载用户类路径(ClassPath)下的类库

类的双亲委派模型

这些类加载器之间的关系如下:

 

类加载器

 

双亲委派模型中,除了顶层的BootstrapClassLoader,其他类加载器都要求有自己的父类加载器,这里的类加载器一般以组合的方式实现。

  • 双亲委派模型的工作过程是:当一个类加载器收到一个类加载请求的时候,他首先不会自己加载这个类,而是将这个请求委派给父类加载器去完成,只有当父类加载器无法完成这个加载请求时,子加载器才会尝试自己去加载。
  • 双亲委派模型的作用:
    • 使得java类随着它的类加载器一起具备了一种带有优先级的层次关系
    • 保证Java环境的稳定性
    • 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。

三、Android中的ClassLoader

由于Android虚拟机的执行文件是Dex文件,而不是JVM中的Class文件,所以Java是中的类加载器是无法加载Dex文件的,因此,Android中存在另外一套ClassLoader。

1、Android的ClassLoader类型

Android中的ClassLoader根据用途可分为一下3种:

  1. BootClassLoader:主要用于加载系统的类,包括java和android系统的类库,和JVM中不同,BootClassLoader是ClassLoader内部类,是由java实现的,它也是所有系统ClassLoader的父ClassLoader
  2. PathClassLoader:用于加载Android系统类和开发编写应用的类,只能加载已经安装应用的dex或apk文件,也是getSystemClassLoader的返回对象
  3. DexClassLoader:可以用于加载任意路径的zip,jar或者apk文件,也是进行安卓动态加载的基础

1.1、Android中ClassLoader的继承关系

1.2、ClassLoader的执行过程

从上面的ClassLoader结构图可以看到,ClassLoader的主要逻辑集中在ClassLoader和BaseDexClassLoader两个类中

1.2.1、ClasLoader

1、ClassLoader是所有ClassLoader的父类,它定义了加载Class的一般行为。

2、与Java中不同,Android中加载类的过程主要是由loadClass方法实现,而在Java中则是findClass方法。

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);
    if (clazz == null) {
        ClassNotFoundException suppressed = null;
        try {
            clazz = parent.loadClass(className, false);
        } catch (ClassNotFoundException e) {
            suppressed = e;
        }
        if (clazz == null) {
            try {
                clazz = findClass(className);
            } catch (ClassNotFoundException e) {
                e.addSuppressed(suppressed);
                throw e;
            }
        }
    }
    return clazz;
}

3、可以看到,当收到一个加载类请求的时候,ClassLoader会先调用findLoaderClass查询是否被本加载器加载过(调用JNI方法),如果没有被加载则把请求委托给父类加载器,当父类加载器无法完成加载行为的时候,才会调用findClass方法尝试自己加载,而ClassLoader中的findClass方法并没有实现,而是交给子类去实现。

1.2.2、BaseDexClassLoader

1、作为ClassLoader的直接子类,BaseDexClassLoader实现加载findClass方法的主要逻辑,而其子类DexClassLoader和PathClassLoader都只是加载路径以及某些行为不同而已

2、可以看到findClass方法又是调用了pathList的findClass方法去加载类,而pathList则是一个DexPathList对象,它的findClass对象是这样实现的:

3、其中DexFile是Dex文件在Java中的表现形式,而它的loadClassBinaryName方法则是最后调用了JNI方法去完成在dex文件在加载Class对象。

1.2.3、DexClassLoader和PathClassLoader

1、DexClassLoader和PathClassLoader都是BaseDexClassLoader的子类,他们的实现也很简单,只是构造方法传入了不同的参数而已:

  • DexClassLoader 

  • PathClassLoader

2、可以看到,DexClassLoader和PathClassLoader的区别就是,PathClassLoader的第二个参数传为NULL,回到BaseDexClassLoader中可以看到:

3、根据注释可以看到optimizedDirectory参数是用来放置DexFile的,那么具体是怎么回事呢,再进去DexPathList

4、optimizedDirectory被传进了makeDexElements方法

5、又被传进了loadDexFile

6、可以看到,如果optimizedDirectory为NULL,则会以原来的路径创建DexFile,否则会以optimizedDirectory为路径创建DexFile

7、其实optimizedDirectory是要求一个内部路径的,因为动态加载去加载的可执行文件一定要存放在内部存储。而DexClassLoader可以指定optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部路径的dex,也就是存在于以及安装过的apk里面的。

参考:https://www.jianshu.com/p/144df8826d15

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值