前言
本篇开始分析classloader。
参考:
https://blog.csdn.net/itachi85/article/details/78088701
https://blog.csdn.net/itachi85/article/details/78276837?utm_source=blogxgwz0
http://gityuan.com/2017/03/19/android-classloader/
https://www.jianshu.com/p/2f9b1d8be88d
https://www.jianshu.com/p/3912c05d257e
https://www.jianshu.com/p/20dcfcf27004
https://www.jianshu.com/p/b89d0b03e82c
http://liwenkun.me/2016/11/11/android-load-class-dynamically/
jvm
先从java虚拟机的类加载器讲起。以后再补java虚拟机的规范。
java虚拟机的系统加载器有:
Bootstrap ClassLoader(引导类加载器)
用C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang.、java.uti.等这些系统类。它用来加载以下目录中的类库:
$JAVA_HOME/jre/lib目录
-Xbootclasspath参数指定的目录
Java虚拟机的启动就是通过引导类加载器创建一个初始类来完成的。由于类加载器是使用平台相关的底层C/C++语言实现的, 所以该加载器不能被Java代码访问到。但是我们可以查询某个类是否被引导类加载器加载过。
Extensions ClassLoader(拓展类加载器)
用于加载 Java 的拓展类 ,用来提供除了系统类之外的额外功能。它用来加载以下目录中的类库:
加载$JAVA_HOME/jre/lib/ext目录
系统属性java.ext.dir所指定的目录。
Application ClassLoader(应用程序类加载器)
又称作System ClassLoader(系统类加载器),这是因为这个类加载器可以通过ClassLoader的getSystemClassLoader方法获取到。它用来加载以下目录中的类库:
当前应用程序Classpath目录
系统属性java.class.path指定的目录
用户自定义加载器,则是通过继承 java.lang.ClassLoader类的方式来实现自己的类加载器。
除了 Bootstrap ClassLoader,Extensions ClassLoader和App ClassLoader也继承了java.lang.ClassLoader类。
类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
其父都是创建时存储的。
采取双亲委托模式主要有两点好处:
- 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。 因为所有加载器都被连接在了一起,加载类时一定先向上委托,再向下加载,因此保证了只加载一次
- 更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改 类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易。
android中的classloader
PathClassLoader: 主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
DexClassLoader: 可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载,但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多插件化方案都是采用DexClassLoader;
BaseDexClassLoader: 比较基础的类加载器, PathClassLoader和DexClassLoader都只是在构造函数上对其简单封装而已.
BootClassLoader: 作为父类的类构造器.
还有几个提供的相关类:
- ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类。
- SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
- URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
- InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
- BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。
ClassLoader
ClassLoader:
libcore\ojluni\src\main\java\java\lang\ClassLoader.java
BootClassLoader
Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的BootClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的
libcore\ojluni\src\main\java\java\lang\ClassLoader.java
BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。
PathClassLoader
Android系统使用PathClassLoader来加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件.
libcore\dalvik\src\main\java\dalvik\system\PathClassLoader.java
dexPath:dex文件以及包含dex的apk文件或jar文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。
librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null。
parent:ClassLoader的parent。
DexClassLoader
DexClassLoader可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也就意味着DexClassLoader可以在应用未安装的情况下加载dex相关文件。
libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
DexClassLoader构造方法的参数要比PathClassLoader多一个optimizedDirectory参数,该参数表示dex的优化存储odex的路径,PathClassLoader默认路径为/data/dalvik-cache
BaseDexClassLoader
BaseDexClassLoader
libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
path生成pathList,pathList是每次BaseDexClassLoader创建都会创建。代表加载的所有文件与目录
源码分析
2-DexPathList
DexPathList
libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
收集:
dexElements: 记录所有的dexFile文件/目录
nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库
有一个问题,什么时候从apk、jar中解压的?DexClassLoader的说明中说了可以是压缩包的,虽然我试的时候失败了
3-makeDexElements
makeDexElements
libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
Element对象表示目录或着DexFile对象。makeDexElements通过解析传过来的目录列表将其下对象挨个纳入Element,对于文件调用loadDexFile加载成内存DexFile对象。
值得注意的是是不是.dex结尾都会调用loadDexFile。
4-loadDexFile
loadDexFile
libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
构建DexFile返回
5-DexFile
DexFile
libcore\dalvik\src\main\java\dalvik\system\DexFile.java
可见DexFile代表的就是一个dex文件加载到内存中。参数:
- dex文件的绝对路径
- outputName:null
- flags:0
- ClassLoader就是加载器this
- DexPathList.Element[]要创建的文件/目录节点数组
7-openDexFileNative
我们还是跟进native接着看
openDexFileNative
D:\androidwsp\android8.0.0_r1\android-8.0.0_r1\art\runtime\native\dalvik_system_DexFile.cc
参:
- 文件名的c字串模式
- jobject的class_loader
- jobjectArray的dex_elements
- 出:OatFile指针
- 字串向量error_msgs
10-ConvertDexFilesToJavaArray
ConvertDexFilesToJavaArray
返回给mCookie,oat与dex结构如何转化的:
art\runtime\native\dalvik_system_DexFile.cc
8-OpenDexFilesFromOat
OpenDexFilesFromOat
art\runtime\oat_file_manager.cc
将dex_location生成OatFileAssistant(助手),查看是否为oat文件,决定dex到oat文件的优化,之后从磁盘中读取oat到内存,从oat中读取dex,并返回oat与dex。
值的注意的是如果hook了execv函数阻止oat文件的生成,最终会调用DexFile::Open方法来直接加载原始的DEX文件。
9-OatFileAssistant
OatFileAssistant
art\runtime\oat_file_assistant.cc
代表文件对象,在构造中通过dex路径获取了odex和oat的路径
11-IsUpToDate
IsUpToDate
art\runtime\oat_file_assistant.cc
如果返回值==kOatUpToDate就表示优化过了,kOatUpToDate定义在一个enum,用于表示文件的状态。IsUpToDate中,Status检测(状态)
这里检测过程之后还比较长,先留下等用到再看。
13-MakeUpToDate
接下来看MakeUpToDate
art\runtime\oat_file_assistant.cc
获取、设置路径,执行优化
info.Filename()
art\runtime\oat_file_assistant.cc
15-Dex2Oat
Dex2Oat
art\runtime\oat_file_assistant.cc
进一步添加参数,调用Exec,其中参数第一项是/bin/dex2oat。
16-Exec
Exec
art\runtime\exec_utils.cc
fork出子进程,子进程调用dex2oat执行优化,父进程等待子结束。