Android 中的类文件和类加载器详情


Posted in Java/Android onJune 05, 2022

一、Java中的类加载器

首先花点时间回顾一下Java中的三种类加载器:

  • BootStrap ClassLoader 启动类加载器,它是实现自C/C++的类加载器,用于加载JDK的核心类库,例如java.langjava.util等系统类。JVM的启动需要通过BootStrap ClassLoader来创建一个初始类来完成
  • Extensions ClassLoader 扩展类加载器,ExtClassLoader,它用于加载一些系统之外的额外功能。
  • Application ClassLoader 应用类加载器,也称作系统类加载器,它可以通过getSystemClassLoader拿到,也可以称为系统类加载器。
  • Custom ClassLoader 自定义类加载器,我们可以通过继承java.lang.ClassLoader类来实现自己的类加载器,注意,ExtClassLoader和AppClassLoader也是继承自java.lang.ClassLoader类的。

总体的加载关系是:null->ExtClassloader->AppClassLoader,需要注意的是,BootStrap ClassLoader无法在Java中通过.class.getClassLoader()访问它的父加载器。

同时,这只代表加载关系,而不代表继承关系,ClassLoader的继承关系如下:

Android 中的类文件和类加载器详情

其中:

  • ClassLoader本身是一个抽象类,定义了主要的一些功能。
  • SecureClassLoader扩展实现了ClassLoader方面加入权限方面的功能,加强了ClassLoader的安全性。
  • URLClassLoader通过URL路径,从Jar文件和文件夹下加载类和资源。
  • ExtClassLoader和AppClassLoader,本身都是Launcher应用的内部类,Launcher是Java虚拟机的入口应用,二者都在Launcher中初始化;

双亲委派机制,大致意思就是当一个类加载器去加载某个类时,会优先委托给父加载器去进行加载,而不是自己加载。这样能够有效地保护加载类的安全性,比如我们希望加载一个java.lang.String类,在双亲委派机制下,我们就会优先由父类进行加载,而不是我们自己的类加载器去做加载,父类加载器会从指定的,安全的目录下查找String类。如果是我们自己的类加载器中加载该类那么可能会出现一些安全方面的问题。换句话说,你自己定义的java.lang.String类是无法被加载的。当然,这个前提是,你得遵循双亲委派机制,如果你重新写个类加载器,自定义了loadClass并且不遵循双亲委派机制,那么双亲委派机制就被打破了。这也是为什么,JVM认为两个类相等不仅仅要类名相等,而且要类加载器相等才是同一个类。

二、Android中的类加载器

区别于标准JVM以Class文件为输入的字节码文件,Android虚拟机采用更为紧凑的Dex文件作为输入文件,无论是Dalvik VM还是ART VM。这样一来,类加载器自然也会有所改变。

Android中的类加载器类型被分为两类:

  • 系统类加载器:PathClassLoader、DexClassLoader、BootClassLoader
  • 自定义类加载器

2.1 BootClassLoader

Android 系统启动时,会使用BootClassLoader来加载常用的类,与SDK中的BootStrap ClassLoader不同的是,它本身不是由C/C++实现的, 而是采用Java实现的,它是ClassLoader的内部类,继承自ClassLoader,本身是一个单例类。应用中无法直接访问到BootClassLoader。

BootClassLoader是在ZygoteInit的入口方法中,间接调用了preloadClasses方法中,进行创建的,Android在/framework/base/preloaded-classes中封装了一系列的预加载类的目录,一些常用类,例如:ContextImpl、Fragment、Dialog等等都在列。预加载之后,应用程序启动时,就不用额外去做加载了。

2.2 PathClassLoader

PathClassLoader它通常被系统用来加载Apk中自带的Dex文件,它的构造函数中少了一个参数:optimizedDirectory,这是因为PathClassLoader定义了默认的optimizedDirectory参数:/data/dalvik-cache/,因此,我们无法自定义Dex文件的解压路径,所以我们加载类时,一般都使用DexClassLoader

PathClassLoader是Zygote进程在fork SystemServer进程时创建的,当Zygote进程在新创建SystemServer时,通过调用forkSystemServer方法时,会调用到handleSystemServerProcess(),然后调用createPathClassLoader()去创建PathClassLoader。

2.3 DexClassLoader

可以在磁盘中加载.dex或者是.apk文件,但是本质上都是加载属于Android 的字节码文件:Dex文件。

它的构造方法有四个参数:

  • dexPath:相关的文件路径;支持多个路径,使用「:」分割
  • optimizedDirectory:Dex文件解压后的文件存储路径,一般情况下使用当前文件的私有路径。

"this parameter is deprecated and has no effect since API level 26."

注意:optimizedDirectory参数在API26之后被废弃了

  • librarySearchPath:包含C/C++库的路径集合,可以为null
  • parent:父加载器

我们通常在App启动时,我们通常使用DexClassLoader动态加载Dex的方式来实现应用程序Java代码层面的热修复。

2.4 InMemoryDexClassLoader

Android8.0 中新增的用于加载内存中的类加载器。和PathClassLoader、DexClassLoader一样,都是BaseDexClassLoader的实现类。

三、Dex文件

3.1 Android内存中的Dex文件

BaseDexClassLoader有三个子类:DexClassLoader、PathClassLoader、InMemoryDexClassLoader,它们三个主要任务就是:加载外部的Dex文件,获取其中定义的类信息。同样,Android的类加载机制也遵循双亲委派机制

BaseDexClassLoader有一个特殊的结构:DexPathList类型的pathList ,它内部维护了一个Element类型的数组,用来存储被加载的Dex文件信息:

private Element[] dexElements;

每当我们要使用一个类时,类加载器就会先检索:DexPathList中的所有Dex文件,逐个遍历,看看其中是否含有所需要的类:

Android 中的类文件和类加载器详情

Element内部有一个对象:DexFile,在调用Element#findClass时,会按照如下规则去查找:

Android 中的类文件和类加载器详情

而最终,loadClassBinaryName会调用Native代码在本地内存上创建一个指向Dex文件的对象,这样 ,我们知道Dex文件在内存中的引用是类加载下的DexPathList中的一个个Element。

这个Element数组可以作为一个热修复的接入点,我们知道,类加载只会被加载一次,如果此时我们有多个Dex文件,那么Dex文件的引用在Element中会按照加载的顺序排列,这样一来,排在前面的Dex类中的Class就会被优先加载,由此我们就可以将热修复后重新生成的Patch.dexPatch.apk加载到用户手机存储空间当中,然后自行使用DexPathClassLoader进行加载,并通过反射,Hook掉PathClassLoader,将Patch.dex对应的Element反射插入到其DexPathList当中去。这样一来,加载时就会优先从Patch.dex中加载了,原理大致如下图。修复后加载原先出错的类ClassE将会从Patch.dex中优先加载,而出错的Class E由于类加载的特性,将不会被加载出来。

Android 中的类文件和类加载器详情

3.2 Dex文件的生成

我们所编写的Java代码,使用Java自带的编译器编译完成之后默认的输出一定是.class文件,而在ART或者Dalvik虚拟机中需要输入Dex文件,那么在其中必然存在Class -> Dex文件的过程。

该过程是由d8工具完成的,在我们的SDK目录下:/Library/Android/sdk/build-tools/28.0.3,有非常多和我们Android 构建相关的一些工具,例如aapt2工具会负责将res.xml中的文件在R.java中生成对应的ID引用、负责将二进制资源、资源表resources.arsc、classes.dex以及assets集成打包进一个未签名的.apk文件内。

d8工具会将需要打包的.class文件、额外依赖的Jar文件一同参与编译。比如我们需要对appt2下的一个MainActivity.class文件进行编译,那么我们可以在一个新项目中,点击Android Studio的编译或者上面的绿色小锤子,然后在:MyApplication2/app/build/intermediates/javac/debug/classes/com/red/myapplication 下我看到MainActicity.class文件,然后在该目录下执行如下的指令,注意,将MainActivity的AppcompatActivity换成Activity,因为前者是属于AndroidX系列的的AAR包中的依赖,需要额外添加。

d8 aapt/MainActivity.class
 --lib /Library/Android/sdk/platforms/android-29/android.jar
 --output ./

其中,--lib指定了一些额外的依赖,因为MainActivity中会依赖android.jar中的一些文件(比如Activity类),完成后,我们就得到了一个classes.dex文件,结构如下:

Android 中的类文件和类加载器详情

如上的步骤都在Android提供的Gradle套件中,帮我们完成了,Gradle插件依赖的本质,就是插件文件的下载(Gradle同步)和引用。

到此这篇关于Android 中的类文件和类加载器详情的文章就介绍到这了,更多相关Android 类文件 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Java/Android 相关文章推荐
Java实现多线程聊天室
Jun 26 Java/Android
Springboot使用Spring Data JPA实现数据库操作
Jun 30 Java/Android
java泛型通配符详解
Jul 25 Java/Android
一篇文章带你学习Mybatis-Plus(新手入门)
Aug 02 Java/Android
IDEA2021.2配置docker如何将springboot项目打成镜像一键发布部署
Sep 25 Java/Android
JVM的类加载器和双亲委派模式你了解吗
Mar 13 Java/Android
Java 超详细讲解数据结构中的堆的应用
Apr 02 Java/Android
SpringCloud项目如何解决log4j2漏洞
Apr 10 Java/Android
IDEA 2022 Translation 未知错误 翻译文档失败
Apr 24 Java/Android
Java异常体系非正常停止和分类
Jun 14 Java/Android
一文搞懂Java中的注解和反射
Jun 21 Java/Android
HttpClient实现文件上传功能
Aug 14 Java/Android
Android中的Launch Mode详情
Spring中的@Transactional的工作原理
Jun 05 #Java/Android
Qt数据库应用之实现图片转pdf
Ubuntu18.04下QT开发Android无法连接设备问题解决实现
Jun 01 #Java/Android
openGauss数据库JDBC环境连接配置的详细过程(Eclipse)
Jun 01 #Java/Android
Java中生成微信小程序太阳码的实现方案
Jun 01 #Java/Android
Java处理延时任务的常用几种解决方案
You might like
人工智能开始玩《星际争霸2》 你的操作跟得上吗?
2017/08/11 星际争霸
php $_SERVER当前完整url的写法
2009/11/12 PHP
php简单获取目录列表的方法
2015/03/24 PHP
Zend Framework教程之资源(Resources)用法实例详解
2016/03/14 PHP
php自动提交表单的方法(基于fsockopen与curl)
2016/05/09 PHP
PHP连接MySQL进行增、删、改、查操作
2017/02/19 PHP
jquery imgareaselect 使用利用js与程序结合实现图片剪切
2009/07/30 Javascript
jQuery Ajax请求状态管理器打包
2012/05/03 Javascript
jQuery基本过滤选择器使用介绍
2013/04/18 Javascript
nodejs导出excel的方法
2015/06/30 NodeJs
基于jQuery实现select下拉选择可输入附源码下载
2016/02/03 Javascript
MUI 上拉刷新/下拉加载功能实例代码
2017/04/13 Javascript
vue数字类型过滤器的示例代码
2017/09/07 Javascript
Angular搜索 过滤 批量删除 添加 表单验证功能集锦(实例代码)
2017/10/25 Javascript
JS实现json对象数组按对象属性排序操作示例
2018/05/18 Javascript
基于vue-upload-component封装一个图片上传组件的示例
2018/10/16 Javascript
JS插入排序简单理解与实现方法分析
2019/11/25 Javascript
JS实现拖拽元素时与另一元素碰撞检测
2020/08/27 Javascript
vue中axios封装使用的完整教程
2021/03/03 Vue.js
[02:25]专访DOTA2负责人Erik 国际邀请赛暂不会离开西雅
2014/07/21 DOTA
[01:02]DOTA2辉夜杯决赛日 CDEC.Y对阵VG赛前花絮
2015/12/27 DOTA
go和python调用其它程序并得到程序输出
2014/02/10 Python
Python中的ceil()方法使用教程
2015/05/14 Python
Python学习笔记之列表和成员运算符及列表相关方法详解
2019/08/22 Python
解决Python spyder显示不全df列和行的问题
2020/04/20 Python
Python astype(np.float)函数使用方法解析
2020/06/08 Python
实习护士自我鉴定
2013/10/13 职场文书
生物科学系大学生的自我评价
2013/12/20 职场文书
家长寄语大全
2014/04/02 职场文书
幼儿园家长安全责任书
2014/07/22 职场文书
工作证明英文模板
2014/10/21 职场文书
社区六一儿童节活动总结
2015/02/11 职场文书
导游词400字
2015/02/13 职场文书
《金钱的魔力》教学反思
2016/02/20 职场文书
Mysql官方性能测试工具mysqlslap的使用简介
2021/05/21 MySQL
vue如何清除浏览器历史栈
2022/05/25 Vue.js