关于Android和Java的ClassLoader相关知识盘点
已经很久没有写博客,就再利用这段时间把该以前总结过的又再总结一次吧,好多东西光记在脑子里也不行。那首先就用Android的ClassLoader开个头,接着就再总结一下Volley, Retrofit, OKHttp, Rxjava, HashMap,Gson,Fresco,动态加载,热点修复以及算法吧。
Java中的Classloader
分类
- Classloader:这个类是一个抽象类,除了Bootstrap classloader之外所有的classloader都继承它
- Bootstrap Classloader:这个加载器是由C++编写的,在Java虚拟机启动后初始化,主要负责加载核心类库。它并不是Classloader的子类,而是由JVM自己实现的一个加载器,嵌在JVM当中
- Extension Classloader:它是由Bootstrap classloader加载而来,同时也是它的子类,由Java编写,主要负责加载扩展的Javaclass
- System Classloader:它也是由java编写,主要负责加载应用程序自身的类
程序加载的流程
当运行一个程序的时候,JVM启动,然后加载Bootstrap Classloader,该ClassLoader加载Java核心API,也就在这个时候加载了ExtClassloader和AppClassloader,之后就调用ExtClassloader去加载拓展API,最后AppClassloader加载CLASSPATH目录下定义的classes
双亲委派模式
当我们需要使用到一个类时,就会加载该类(也就是动态加载)。
首先JVM就会使用当前线程的类加载向上递归,也就是找到它的父类,如果父类能够加载,那么就由父类来加载,依次递归,如果不能,就再下传给子类。
为什么会存在双亲委派模式
在Java中,我们判断两个类是不是同一个类,实际上就是判断他们两个是不是由同一个类加载器加载而来的。如果同一个类A,被两个不同的加载器加载,那么就会生成两份A的字节码,而Java就会认为这两个A并不是同一个类。java判断两个类一样的条件: classloader id + package id + class id。
因此,双亲委派模式的意义就出来了:为了防止内存中出现多份同样的字节码。类A和类B都想加载system类,如果没有双亲委派的话,那么它们分别使用自己的加载器去加载就会产生两份一模一样的字节码。而如果使用了双亲委派模式,首先就会尝试将System类交给Bootstrap类加载,A使用它加载完毕后,当B再来请求加载时,BootStrap就可以直接返回内存中已经加载了的System类了
P.S : 在开发中是否还遇到过这样的一种情况?类A想通过类型强转的方式转换为类B,但是编译报错,提示不能转换,这个其实也就是因为他们是由不同的类加载器加载而来,存在一个壁垒
Android中的Classloader
Android虚拟机dalvik,现在已经换成ART,也是一种虚拟机,类似于JVM,他们也都实现了双亲委派模式用来防止出现多份重复的字节码,同样由于Android自身的一些特性以及虚拟机的选择,它们是存在一些区别的
Android下面的字节码文件在编译完成后会再被打包成dex类的文件,同时经过dexopt的优化(优化Class文件中的各种函数表,变量表等等)形成odex文件,它本质也还是class文件,因此加载它的话肯定就需要特殊的类加载器来加载咯
首先,最开始也是我们的Classloader抽象类,它有一个子类叫做BaseDexClassloader,而这个类呢又是DexClassloader和PathClassloader的父类。当然同时也存在着SystemLoader, BootClassloader这些其他的加载器。
1 | public class BaseDexClassLoader extends ClassLoader { |
1 | public abstract class ClassLoader { |
上面的这个是SystemClassloader,用来加载classpath中的类的。熟悉的延迟加载单例模式
DexClassLoader
这个类是用来加载一些还没有被安装的字节码文件的。因此不管是动态加载还是热点修复,都是使用这个类加载器来加载从服务器获得的apk中的文件。关于动态加载和热点修复在以后的博客中会有介绍,其实本来好早之前就打算写一个动态加载的库的,github上我库都建好了,但是临时来了点事就没做了,找个时间补回来
1 | public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { |
PathClassloader
这个类就是用来加载所有已经被安装被安装好了的字节码文件
1 | public class PathClassLoader extends BaseDexClassLoader { |