安卓类加载机制
目录
- 一、ClassLoader介绍
- 二、双亲委托机制
- 三、类的加载过程
一、ClassLoader介绍
任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。
BootClassLoader
用于加载Android Framework层class文件。
class Class<T> {
...
private transient ClassLoader classLoader;
...
}
ClassLoader是一个抽象类,而它的具体实现类主要有:

- BootClassLoader
用于加载Android Framework层class文件。- PathClassLoader
用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex- DexClassLoader
用于加载指定的dex,以及jar、zip、apk中的classes.dex
很多博客里说PathClassLoader只能加载已安装的apk的dex,其实这说的应该是在dalvik虚拟机上。但现在一般不用关心dalvik了。
示例
Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加载");
Log.e(TAG, "MainActivity.class 由:" + getClassLoader() +" 加载");
输出:
com.MyStudy.HomeScreen E Activity.class 由:java.lang.BootClassLoader@d7628d5 加载
com.MyStudy.HomeScreen E MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.MyStudy.HomeScreen-GRRgDXx9OKqZObuWM0RBgg==/base.apk”],nativeLibraryDirectories=[/data/app/com.MyStudy.HomeScreen-GRRgDXx9OKqZObuWM0RBgg==/lib/x86, /system/lib, /system/product/lib]]] 加载
它们之间的关系如下:

PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader 。
public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);}
}public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}
可以看到两者唯一的区别在于:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建为 File 对象传给 super ,而 PathClassLoader 则直接给到null。因此两者都可以加载指定的dex,以及jar、zip、apk中的classes.dex
PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());
File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(),null,getClassLoader());
其实, optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。
在API 26源码中,将DexClassLoader的optimizedDirectory标记为了 deprecated 弃用,实现也变为了:
public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
......和PathClassLoader一模一样了!
二、双亲委托机制
可以看到创建 ClassLoader 需要接收一个 ClassLoader parent 参数。这个 parent 的目的就在于实现类加载的双亲委托。即:
某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
优点:
- 1、 避免重复加载, 当父加载器已经加载了该类的时候, 就没有必要子ClassLoader再加载一次。
- 2、 安全性考虑, 防止核心API库被随意篡改。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized(this.getClassLoadingLock(name)) {// 检查class是否有被加载Class<?> c = this.findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (this.parent != null) {//如果parent不为null,则调用parent的loadClass进行加载c = this.parent.loadClass(name, false);} else {//parent为null,则调用BootClassLoader进行加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException var10) {}if (c == null) {// 如果都找不到就自己查找long t1 = System.nanoTime();c = this.findClass(name);PerfCounter.getParentDelegationTime().addTime(t1 - t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);PerfCounter.getFindClasses().increment();}}if (resolve) {this.resolveClass(c);}return c;}}
1、 首先调用findLoadedClass检查传入的类是否已经被加载,如果已经加载那么就直接返回。
2、 如果第一步中类没有被加载(c == null),那么就会判断parent是否等于null,也就是判断父加载器是否存在,如果父加载器存在,就调用父加载器的loadClass方法。
3、 如果父加载器不存在就会调用findBootstrapClassOrNull,这个方法会直接返回null。
4、 如果到了第4步依然c == null,那么表示在向上委托的过程中,没有加载该类,会调用findClass继续向下进行查找。
三、类的加载过程
可以看到在所有父ClassLoader无法加载Class时,则会调用自己的findClass()方法。其实任何ClassLoader子类,都可以重写loadClass()与findClass() 。一般如果你不想使用双亲委托,则重写loadClass()修改其实现。而重写findClass()则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义自己如何去查找一个Class。如果没有重写的话,那findClass()是在BaseDexClassLoader中实现的。我们来看一下findClass()中的实现逻辑。
BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();//查找指定的classClass c = pathList.findClass(name, suppressedExceptions);if (c == null) {ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);for (Throwable t : suppressedExceptions) {cnfe.addSuppressed(t);}throw cnfe;}return c;}
这里省略了部分代码,可以看到调用了pathList的findClass()方法,pathList就是DexPathList对象,在BaseDexClassLoader初始化的时候被创建。接下来进入DexPathList。
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
//.........
// splitDexPath 实现为返回 List<File>.add(dexPath)
// makeDexElements 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
//.........
}
可以看到DexPathList在创建的时候调用了makeDexElements()方法来创建出了dexElements数组,在makeDexElements之前我们先来看一下splitDexPath()方法,在这个方法中将dexPath目录下的所有程序文件转变成一个File集合,而且dexPath是一个用冒号作为分隔符把多个程序文件目录拼接起来的字符串,如 /data/dexdir1:/data/dexdir2:…。makePathElements方法核心作用就是将指定路径中的所有文件转化成DexFile同时存储到到Element[]这个数组中。接下来进入DexPathList的findClass()方法中。
public Class<?> findClass(String name, List<Throwable> suppressed) {for (Element element : dexElements) {Class<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}
在findClass()方法中会通过for循环不断的遍历dexElement数组,拿到element,然后调用element的findClass(),Element是DexPathList的内部类,dexElements是维护dex文件的数组, 每一个Element对应一个dex文件。DexPathList遍历dexElements,从每一个dex文件中查找目标类,在找到后即返回并停止遍历。继续往下看,进入element的findClass()方法。
public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;}
如果dexFile不等于空,就去查找类名与name相同的类,否则返回null,dexFile就是用来描述dex文件的,Dex的加载以及Class的查找,都是由该类调用它的native方法完成的。

相关文章:
安卓类加载机制
目录 一、ClassLoader介绍二、双亲委托机制三、类的加载过程 一、ClassLoader介绍 任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加…...
FPGA高端项目:FPGA基于GS2971的SDI视频接收+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持
目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案…...
[计算机网络]--五种IO模型和select
前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、五种IO…...
【力扣经典面试题】14. 最长公共前缀
目录 一、题目描述 二、解题思路 三、解题步骤 四、代码实现(C版详细注释) 五、总结 欢迎点赞关注哦!创作不易,你的支持是我的不竭动力,更多精彩等你哦。 一、题目描述 编写一个函数来查找字符串数组中的最长公共前缀。…...
Linux操作系统的vim常用命令和vim 键盘图
在vi编辑器的命令模式下,命令的组成格式是:nnc。其中,字符c是命令,nn是整数值,它表示该命令将重复执行nn次,如果不给出重复次数的nn值,则命令将只执行一次。例如,在命令模式下按j键表…...
SpringCloudGateway工作原理与链路图
SpringCloudGateway基本介绍 Spring Cloud Gateway 构建于Spring Boot 2.x、 Spring WebFlux和Project Reactor之上。因此,在使用 Spring Cloud Gateway 时,您可能不会应用许多熟悉的同步库(例如 Spring Data 和 Spring Security)和模式。 Spring Cloud Gateway 需要 Sprin…...
VUE2与VUE3之间的主要区别
当谈到 Vue.js 的版本时,Vue 2 和 Vue 3 是最常被提及的两个版本。下面是 Vue 2 和 Vue 3 之间的一些主要区别: 1. 性能提升: Vue 3 在底层核心重写了响应式系统,采用了 Proxy 对象,大幅提高了性能。Vue 3 还引入了静…...
CSS浮动实战,经典好文
零基础学web前端开发要怎么去学? 首先要学习的就是基础知识:html、css和JavaScript。HTML是内容,CSS是表现,JavaScript是行为。前端开发的门槛其实非常低,与服务器端语言先慢后快的学习曲线相比,前端开发的学习曲线是…...
如何搭建Nacos集群
1.搭建Nacos集群 众所周知,在实际的工作中,Nacos的生成环境下一定要部署为集群状态 其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。 我们计划的集群结构: 我就直接在本机上开三个Nacos来搭…...
未来已来!AI大模型引领科技革命
未来已来!AI大模型正以惊人的速度引领着科技革命。随着科技的发展,人工智能在各个领域展现出了非凡的能力和潜力,大模型更是成为了科技领域的明星。从自然语言处理到图像识别,从智能推荐到语音识别,大模型的应用正在改…...
VBA如何记录单元格中字符内容和格式
实例需求:Excel单元格中的字符可以设置不同的字体格式(大小、颜色等),有时需要将这些信息保存起来,以便于后续代码的处理(例如替换后恢复原字体颜色,或者统计某种指定格式字符个数等等ÿ…...
逻辑漏洞(pikachu)
#水平,垂直越权,未授权访问 通过个更换某个id之类的身份标识,从而使A账号获取(修改、删除)B账号数据 使用低权限身份的账号,发送高权限账号才能有的请求,获得其高权限操作 通过删除请求中的认…...
阿里云服务器2核4G多少钱?支持多少在线?并发数性能测试
阿里云2核4G服务器多少钱一年?2核4G配置1个月多少钱?2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年。可以在阿里云CLUB中心查看 aliyun.club 当前最新2核4G服务器精准报价、优惠券和活动信息。 阿里云官方2…...
粘包与拆包
优质博文:IT-BLOG-CN 一、粘包出现的原因 服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存buffer中,通过buffer刷到数据链路层。因服务端接收数据包时,不能断定数据包1何时结束,就有可能出…...
基于QGIS的研究区域遥感影像裁切下载方法-以岳麓区为例
目录 前言 一、数据说明 1、遥感影像 2、矢量范围 二、按矢量范围导出 1、第一步、导出影像 2、第二步、设置输出格式 3、设置裁切范围 4、设置分辨率 三、按矢量范围掩膜 1、第一步、打开裁剪工具 2、第二步、参数设置 编辑 3、执行掩膜 四、webgis支持 1、生成运行…...
YOLOv8-Openvino-ByteTrack【CPU】
纯检测如下: YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 注:YOLOv8和YOLOv9代码内容基本一致! 全部代码Github&…...
【Linux命令】tload
tload 显示系统负载状况。 详细 tload命令以图形化的方式输出当前系统的平均负载到指定的终端。假设不给予终端机编号,则会在执行tload指令的终端机显示负载情形。 语法 tload (选项)(参数)选项 -s : 指定闲时的…...
Qt 通过pdfium将网络上的pdf显示为图片
前言 遇到个需求,就是在qt客户端显示服务器上的pdf文档,文档以base64格式返回给客户端。以下是实现方法: 1、在pro文件增加以下代码: INCLUDEPATH $$PWD/PDFiumSDK/include/publicDEPENDPATH $$PWD/PDFiumSDK/include/public…...
C语言数据结构与算法——深度、广度优先搜索(DFS、BFS)
目录 一、深度优先搜索(Depth-First-Search 简称:DFS) 无向图的深度优先搜索 有向图的深度优先搜索 二、广度优先搜索(Breadth-First-Search 简称:BFS) 无向图的广度优先搜索 有向图的广度优先搜索 深…...
Golang Channel 详细原理和使用技巧
1.简介 Channel(一般简写为 chan) 管道提供了一种机制:它在两个并发执行的协程之间进行同步,并通过传递与该管道元素类型相符的值来进行通信,它是Golang在语言层面提供的goroutine间的通信方式.通过Channel在不同的 goroutine中交换数据,在goroutine之间…...
光电效应实验避坑指南:从汞灯预热到遏止电压判读,新手常犯的5个错误
光电效应实验避坑指南:从汞灯预热到遏止电压判读的5个关键误区 在大学的物理实验室里,光电效应实验就像一位性格古怪的教授——看似简单明了,实则暗藏玄机。许多同学满怀信心地走进实验室,却在数据采集阶段屡屡碰壁,最…...
OBS多平台直播终极指南:一键同时推流到多个平台的完整教程
OBS多平台直播终极指南:一键同时推流到多个平台的完整教程 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是否曾经想过,为什么每次直播都要为不同的平台单独配…...
如何免费突破网盘限速?8大平台直链下载终极指南
如何免费突破网盘限速?8大平台直链下载终极指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 /…...
如何提升区域科技创新服务效率与资源整合能力?
观点作者:科易网-国家科技成果转化(厦门)示范基地现状概述:区域科技创新服务的成效与短板 在数智化转型加速的背景下,区域科技创新服务体系正经历深刻变革。以数据为核心的生产要素重塑了创新生态,科技成果…...
asnumpy:NPU 原生的 NumPy 体验
如果你用 Python 做 AI 开发,大概率写过类似这样的代码:用 NumPy 预处理数据,把结果转成 PyTorch Tensor,推理完再转回 NumPy 做后处理。这个过程里数据在 CPU 和 NPU 之间来回拷贝了一次,拖慢了整体速度。 CANN 的 a…...
从Quill光标到用户头像:手把手教你为Yjs协同编辑器添加完整的在线用户列表(附状态同步技巧)
从Quill光标到用户头像:构建企业级协同编辑器的完整用户感知系统 在数字化办公场景中,协同编辑器的用户体验往往决定了团队协作效率的上限。当多个用户同时编辑同一份文档时,简单的光标显示已无法满足现代团队对协作透明度的需求。本文将深入…...
Cadence软件安装后找不到图标?别慌,手把手教你从开始菜单启动Capture和Allegro
Cadence软件安装后找不到图标?别慌,手把手教你从开始菜单启动Capture和Allegro 刚完成Cadence软件安装的兴奋感,往往会被桌面上空空如也的现状瞬间浇灭。这就像拿到一台新电脑却发现没有电源键——明明安装了专业EDA工具,却连入口…...
别再只盯着IoU了!深入浅出聊聊边界框回归:从IoU到Shape-IoU的演进与选择
边界框回归的进化论:从IoU到Shape-IoU的技术跃迁与实战选型 当我们在计算机视觉领域谈论目标检测时,边界框回归就像是一场永不停歇的进化竞赛。从最初的IoU开始,这场竞赛已经经历了GIoU、DIoU、CIoU、SIoU等多个技术迭代,而最新登…...
别再让容器‘断网’了!Docker DNS配置保姆级教程(从全局到单容器,含8.8.8.8等常用DNS)
Docker容器网络疑难排查:全方位DNS配置指南与实战技巧 当你正在赶一个紧急项目,突然发现Docker容器无法连接外部API服务,控制台不断抛出"Name or service not known"错误——这种场景对开发者来说再熟悉不过了。容器网络问题&#…...
ADF4350频点锁定与电源滤波实战:为什么你的VCO输出有噪声?加个钽电容试试!
ADF4350频点锁定与电源滤波实战:为什么你的VCO输出有噪声?加个钽电容试试! 在射频电路设计中,ADF4350作为一款集成VCO的宽带频率合成器,因其出色的性能和灵活性广受工程师青睐。然而,许多开发者在实际应用中…...
