安卓类加载机制

目录

  • 一、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方法完成的。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/269931.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深圳五兴科技】Java后端面经

本文目录 写在前面试题总览1、java集合2、创建线程的方式3、对spring的理解4、Spring Boot 和传统 Spring 框架的一些区别5、springboot如何解决循环依赖6、对mybatis的理解7、缓存三兄弟8、接口响应慢的处理思路9、http的状态码 写在前面 关于这个专栏&#xff1a; 本专栏记录…

iOS消息转发流程

当向Objc对象发送消息时&#xff0c;如果找到对象对应的方法&#xff0c;就会进入消息转发流程&#xff0c;给开发者提供一些最后的机会处理消息无法发送问题&#xff0c;以免出现程序崩溃。 1. 回调对象的resolveInstanceMethod方法&#xff0c;在这个方法中&#xff0c;允许开…

LeetCode 热题 100 | 图论(二)

目录 1 基础知识 1.1 什么是拓扑排序 1.2 如何进行拓扑排序 1.3 拓扑排序举例 2 207. 课程表 3 210. 课程表 II 菜鸟做题&#xff0c;语言是 C 1 基础知识 1.1 什么是拓扑排序 含义&#xff1a;根据节点之间的依赖关系来生成一个有序的序列。 应用&#xff1a…

Mybatis实现分页查询数据(代码实操讲解)

在MyBatis中实现分页查询的常见方式有两种&#xff1a;使用MyBatis内置的分页插件如PageHelper&#xff0c;或者手动编写分页的SQL语句。下面我将为你提供两种方式的示例代码。 使用PageHelper分页插件 首先&#xff0c;确保你的项目中已经添加了PageHelper的依赖。在Maven项…

gpt批量工具,gpt批量生成文章工具

GPT批量工具在今天的数字化时代扮演着越来越重要的角色&#xff0c;它们通过人工智能技术&#xff0c;可以自动批量生成各种类型的文章&#xff0c;为用户提供了便利和效率。本文将介绍5款不同的GPT批量工具&#xff0c;并介绍一款知名的147GPT生成工具&#xff0c;以及另外一款…

js SheetJS 合并表格导出到同一个excel中

最近有个需求,我在一个页面显示了4个表格, 然后合并导出到excel文件中 四个表,四个sheet,一个excel文件 最后导出时这样: 实现: 1,页面有个导出的checkbox,勾选则导出,不勾选不处理 2,在一个函数中,集中处理四个表数据获取,并将结果返回出来 //获取数据后返回为…

代码随想录算法训练营第三十四天| 860.柠檬水找零 、406.根据身高重建队列 、452. 用最少数量的箭引爆气球

文章目录 1.柠檬水找零2.根据身高重建队列3.用最少数量的箭引爆气球 1.柠檬水找零 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xf…

(四)关系模型之关系代数

4.1关系代数概述 基于集合&#xff0c;提供了一系列的关系代数操作&#xff1a;并、差、笛卡尔积(广义积)、 选择、投影和更名等基本操作以及交、 连接和关系除等扩展操作&#xff0c;是一种集合思维的操作语言。关系代数操作以一个或多个关系为输入&#xff0c;结果是一个新的…

食品加工生产污废水处理如何达标排放

食品加工行业作为一个重要的工业部门&#xff0c;在生产过程中产生大量的污废水。合理、高效地处理这些污废水&#xff0c;实现达标排放&#xff0c;是保护环境和促进可持续发展的重要举措。本文将探讨食品加工生产污废水处理的方法和措施&#xff0c;以达到达标排放的目标。 首…

FreeRTOS操作系统学习——内存管理

C库函数与FreeRTOS内存管理区别 在C语言的库函数中&#xff0c;有mallc、free等函数可以申请以及释放内存空间&#xff0c;那么这为什么不适用于FreeRTOS的内存管理呢&#xff1f; 不适合用在资源紧缺的嵌入式系统中这些函数的实现过于复杂、占据的代码空间太大并非线程安全的…

小黑长沙特种兵归来,身体比较虚弱的js逆向烧脑之路:招标网搜索数据获取

通过有道翻译逆向的初步尝试&#xff0c;这次尝试一下招标网的数据获取感觉轻松了许多!!加油小黑黑 寻找接口地址(通过响应部分&#xff0c;推断出该部分为搜索数据获取接口) 复制curl&#xff0c;构造Python请求信息 进入https://curlconverter.com/python/&#xff0c;通过c…

HarmonyOS创建项目和应用—设置数据处理位置

项目和应用介绍 关于项目 项目是资源、应用的组织实体。资源包括服务器、数据库、存储&#xff0c;以及您的应用、终端用户的数据等。在您使用部分服务时&#xff0c;您是数据的控制者&#xff0c;数据将按照您设置的数据处理位置来存储在指定区域。 通常&#xff0c;您不需…

HTTPS如何保证数据传输的安全性 以及CA签发证书验签

暴力输出&#xff1a; 越看会越深入&#xff0c;睡前难以想通&#xff0c;后深入研究。得之。 有问题 请留言。 ----------追求内心的富足与平和。日行一善。 亓苏姑娘

停止Tomcat服务的方式

运行脚本文件停止 运行Tomcat的bin目录中提供的停止服务的脚本文件 关闭命令 # sh方式 sh shutdown.sh# ./方式 ./shutdown.sh操作步骤 运行结束进程停止 查看Tomcat进程&#xff0c;获得进程id kill进程命令 # 执行命令结束进程 kill -9 65358 操作步骤 注意 kill命令是…

相机类型的分辨率长宽、靶面尺寸大小、像元大小汇总

镜头的靶面尺寸大于等于相机靶面尺寸。 相机的芯片长这样&#xff0c;绿色反光部分&#xff08;我的手忽略&#xff09;&#xff1a; 基本所有像素的相机的靶面大小都可以在这个表格里面找到。 镜头的靶面尺寸在镜头外表上可以找到&#xff0c;选型很重要&#xff01;

如何在2.2.1版Aduino IDE中开发ESP32

ESP32芯片集成了WIFI和蓝牙&#xff0c;而且关于生态也很不错&#xff0c;越来越多的学习者和开发者选择此类芯片&#xff0c;而不像用keil开发STM32或者51一样&#xff0c;ESP32虽然也有官方的ESP32-IDF开发软甲&#xff0c;但是经过我个人的实操体验&#xff0c;不适合小白或…

Blender和3ds Max哪个会是行业未来?

Blender和3ds Max都是很强大的三维建模和渲染软件&#xff0c;各有各的好处。选择哪个软件更好&#xff0c;要看你的需求、预算、技术水平以及行业趋势等因素。 Blender最大的优点是免费且开源&#xff0c;这对预算有限的个人和小团队来说很有吸引力。它有很多建模工具和功能&…

Unity编辑器功能Inspector快捷自动填充数据和可视化调试

我们有时候可能需要在面板增加一些引用&#xff0c;可能添加脚本后要手动拖动&#xff0c;这样如果有大量的脚本拖动也是不小的工作量 实例 例如&#xff1a;我的脚本需要添加一个Bone的列表&#xff0c;一个个拖动很麻烦。 实现脚本 我们可以用这样的脚本来实现。 public…

GFP-GAN环境搭建推理测试

引子 近期&#xff0c;文生图&#xff0c;wav2lip很火&#xff0c;文生图&#xff0c;见识的太多&#xff0c;不多说了。wav2lip其通过语音驱动唇部动作并对视频质量进行修复&#xff0c;里面一般涉及到三个步骤&#xff0c;文本到语音转化&#xff0c;语音驱动唇部动作&#…

[XJTU-SY-BD]基础03 - 包络谱生成

1.分析结果 包络图是第三行。第一行水平时域信号&#xff0c;第二行垂直时域信号&#xff0c;第三行对第1行信号在故障频点包络计算后的结果。 上方是最终的视图。右下角我把光标对准了低频位置的那个故障峰&#xff0c;与理论值是一致的. 2.源码 2.1 测试例程源码 import …