当前位置: 首页 > news >正文

安卓类加载机制

目录

  • 一、ClassLoader介绍
  • 二、双亲委托机制
  • 三、类的加载过程


一、ClassLoader介绍

任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。

BootClassLoader
用于加载Android Frameworkclass文件。
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 文件组成&#xff0c;在程序运行时&#xff0c;需要将 class 文件加载到 JVM 中才可以使用&#xff0c;负责加载这些 class 文件的就是 Java 的类加…...

FPGA高端项目:FPGA基于GS2971的SDI视频接收+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案…...

[计算机网络]--五种IO模型和select

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、五种IO…...

【力扣经典面试题】14. 最长公共前缀

目录 一、题目描述 二、解题思路 三、解题步骤 四、代码实现&#xff08;C版详细注释&#xff09; 五、总结 欢迎点赞关注哦&#xff01;创作不易&#xff0c;你的支持是我的不竭动力&#xff0c;更多精彩等你哦。 一、题目描述 编写一个函数来查找字符串数组中的最长公共前缀。…...

Linux操作系统的vim常用命令和vim 键盘图

在vi编辑器的命令模式下&#xff0c;命令的组成格式是&#xff1a;nnc。其中&#xff0c;字符c是命令&#xff0c;nn是整数值&#xff0c;它表示该命令将重复执行nn次&#xff0c;如果不给出重复次数的nn值&#xff0c;则命令将只执行一次。例如&#xff0c;在命令模式下按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 的版本时&#xff0c;Vue 2 和 Vue 3 是最常被提及的两个版本。下面是 Vue 2 和 Vue 3 之间的一些主要区别&#xff1a; 1. 性能提升&#xff1a; Vue 3 在底层核心重写了响应式系统&#xff0c;采用了 Proxy 对象&#xff0c;大幅提高了性能。Vue 3 还引入了静…...

CSS浮动实战,经典好文

零基础学web前端开发要怎么去学? 首先要学习的就是基础知识&#xff1a;html、css和JavaScript。HTML是内容&#xff0c;CSS是表现&#xff0c;JavaScript是行为。前端开发的门槛其实非常低&#xff0c;与服务器端语言先慢后快的学习曲线相比&#xff0c;前端开发的学习曲线是…...

如何搭建Nacos集群

1.搭建Nacos集群 众所周知&#xff0c;在实际的工作中&#xff0c;Nacos的生成环境下一定要部署为集群状态 其中包含3个nacos节点&#xff0c;然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。 我们计划的集群结构&#xff1a; 我就直接在本机上开三个Nacos来搭…...

未来已来!AI大模型引领科技革命

未来已来&#xff01;AI大模型正以惊人的速度引领着科技革命。随着科技的发展&#xff0c;人工智能在各个领域展现出了非凡的能力和潜力&#xff0c;大模型更是成为了科技领域的明星。从自然语言处理到图像识别&#xff0c;从智能推荐到语音识别&#xff0c;大模型的应用正在改…...

VBA如何记录单元格中字符内容和格式

实例需求&#xff1a;Excel单元格中的字符可以设置不同的字体格式&#xff08;大小、颜色等&#xff09;&#xff0c;有时需要将这些信息保存起来&#xff0c;以便于后续代码的处理&#xff08;例如替换后恢复原字体颜色&#xff0c;或者统计某种指定格式字符个数等等&#xff…...

逻辑漏洞(pikachu)

#水平&#xff0c;垂直越权&#xff0c;未授权访问 通过个更换某个id之类的身份标识&#xff0c;从而使A账号获取&#xff08;修改、删除&#xff09;B账号数据 使用低权限身份的账号&#xff0c;发送高权限账号才能有的请求&#xff0c;获得其高权限操作 通过删除请求中的认…...

阿里云服务器2核4G多少钱?支持多少在线?并发数性能测试

阿里云2核4G服务器多少钱一年&#xff1f;2核4G配置1个月多少钱&#xff1f;2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年。可以在阿里云CLUB中心查看 aliyun.club 当前最新2核4G服务器精准报价、优惠券和活动信息。 阿里云官方2…...

粘包与拆包

优质博文&#xff1a;IT-BLOG-CN 一、粘包出现的原因 服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存buffer中&#xff0c;通过buffer刷到数据链路层。因服务端接收数据包时&#xff0c;不能断定数据包1何时结束&#xff0c;就有可能出…...

基于QGIS的研究区域遥感影像裁切下载方法-以岳麓区为例

目录 前言 一、数据说明 1、遥感影像 2、矢量范围 二、按矢量范围导出 1、第一步、导出影像 2、第二步、设置输出格式 3、设置裁切范围 4、设置分辨率 三、按矢量范围掩膜 1、第一步、打开裁剪工具 2、第二步、参数设置 ​编辑 3、执行掩膜 四、webgis支持 1、生成运行…...

YOLOv8-Openvino-ByteTrack【CPU】

纯检测如下&#xff1a; YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 注&#xff1a;YOLOv8和YOLOv9代码内容基本一致&#xff01; 全部代码Github&…...

【Linux命令】tload

tload 显示系统负载状况。 详细 tload命令以图形化的方式输出当前系统的平均负载到指定的终端。假设不给予终端机编号&#xff0c;则会在执行tload指令的终端机显示负载情形。 语法 tload &#xff08;选项&#xff09;&#xff08;参数&#xff09;选项 -s : 指定闲时的…...

Qt 通过pdfium将网络上的pdf显示为图片

前言 遇到个需求&#xff0c;就是在qt客户端显示服务器上的pdf文档&#xff0c;文档以base64格式返回给客户端。以下是实现方法&#xff1a; 1、在pro文件增加以下代码&#xff1a; INCLUDEPATH $$PWD/PDFiumSDK/include/publicDEPENDPATH $$PWD/PDFiumSDK/include/public…...

C语言数据结构与算法——深度、广度优先搜索(DFS、BFS)

目录 一、深度优先搜索&#xff08;Depth-First-Search 简称&#xff1a;DFS&#xff09; 无向图的深度优先搜索 有向图的深度优先搜索 二、广度优先搜索&#xff08;Breadth-First-Search 简称&#xff1a;BFS&#xff09; 无向图的广度优先搜索 有向图的广度优先搜索 深…...

Golang Channel 详细原理和使用技巧

1.简介 Channel(一般简写为 chan) 管道提供了一种机制:它在两个并发执行的协程之间进行同步&#xff0c;并通过传递与该管道元素类型相符的值来进行通信,它是Golang在语言层面提供的goroutine间的通信方式.通过Channel在不同的 goroutine中交换数据&#xff0c;在goroutine之间…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...