Android 中的 ClassLoader

Android进阶解密读书笔记(十一)——理解 ClassLoader 定义一个自定义 ClassLoader 并继承抽象类 ClassLoader复写 findClass 方法,并在 findClass 方法中调用 defineClass 方法。 阅读详情

一、ClassLoader

ClassLoader 是用来加载 class 的类,它是一个抽象类。通过一个类的二进制名,一个 classLoader 会尝试去定位或生成组成一个类需要的数据。一个典型的策略是把类名转化为文件名,然后从文件系统读取这个文件名的 class 文件。

二进制名:

"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"

每个 class 对象都包含一个加载它的 classLoader 的引用。

ClassLoader 使用双亲委托模型去搜索类和资源。每个 ClassLoader 的实例都有一个相关联的父加载器。当需要去寻找一个类或资源,一个 ClassLoader 实例在自己去寻找这个类或资源之前,会先委托他的父加载器去寻找。java 虚拟机内置的类加载器叫引导类加载器(BootClassLoader),它没有父加载器,但是可以作为父加载器。

类加载器如果需要支持并发,需要在类初始化的时候通过激活 ClassLoader.registerAsParallelCapable 方法注册。(类加载器默认会注册为可并发)。但是,它的子类如果需要支持,仍然需要自己注册一遍。
在没有严格分级的委托模型环境中,类加载器必须是可并发的,否则类加载可能导致死锁,因为加载器在加载的过程中会拥有锁。

正常来说,java 虚拟机从本地文件系统加载类到独立平台。例如,在 UNIX 系统中,虚拟机从 CLASSPATH 的环境变量所标识的路径下加载类。

但是,一些类可能不是从文件生成的,他们可能从其他源生成,比如网络,或者它们可能通过一个应用来生成。 defineClass 方法将一个字节数组变成一个 class 类的实例,这个新的 class 的实例可以通过 Class.newInstance 方法来生成。

通过类加载器生成的对象的方法和构造器可以引用其他的类。为了决定这个类引用的地方,java 虚拟机激活一开始生成这个类的 ClassLoader 的 loadClass 方法。

例如,一个应用可以创建一个网络类加载器去从服务端下载 class 文件,示例代码:

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();

这个网络类加载器必须定义 findClass 方法。一旦它下载了可以组成类的字节序列,它应该使用 defineClass 方法去生成这个类的实例。示例代码:

class NetworkClassLoader extends ClassLoader {
    String host;
    int port;

    public Class loadClass(String className, boolean resolve) {
        ...
        findClass(className);
        ...
    }
    public Class findClass(String className) {
        byte[] b = loadClassData(className);
        return defineClass(className, b, 0, b.length);
    }
    private byte[] loadClassData(String name) {
        // load the class data from the connection
        . . .
    }
}

1.1 Java 与 Android 的对比

java 中的 ClassLoader 和 Android 中的 ClassLoader 对应关系:
在这里插入图片描述
代码测试:

// 系统加载器
System.out.println(ClassLoader.getSystemClassLoader());
// 自定义类加载器
System.out.println(MyTest.class.getClassLoader());
// 自定义类加载器,的父加载器
System.out.println(MyTest.class.getClassLoader().getParent());
// 自定义类加载器,的父加载器,的父加载器
System.out.println(MyTest.class.getClassLoader().getParent().getParent());
// 系统类加载器
System.out.println(String.class.getClassLoader());

输出:

Java:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7f31245a
null
null

Android:
dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/lib64]]]
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.gdeer.gdtesthub-YaOqSqpCvFgigrjg3u-rRg==/base.apk"],nativeLibraryDirectories=[/data/app/com.gdeer.gdtesthub-YaOqSqpCvFgigrjg3u-rRg==/lib/x86_64, /system/lib64]]]
java.lang.BootClassLoader@5de531
null
java.lang.BootClassLoader@5de531

二、Android 中的 ClassLoader

ClassLoader

2.1 BootClassLoader

Android 中默认无父构造函数传入的情况下,默认父构造器为一个 PathClassLoader 且此 PathClassLoader 父构造器为 BootClassLoader。因此,BootClassLoader 是作为最终父类的类构造器。
BootClassLoader 是 ClassLoader 的内部类,是包内可见,我们无法使用,也不能动态加载。

2.2 URLClassLoader

URLClassLoader 继承于 SecureClassLoader。URLClassLoader 只能用于加载 Jar 文件,但由于 dalvik 不能直接识别 jar,所以在 Android 中无法使用这个加载器。(那 android 中怎么加载 jar 的?)

2.3 BaseDexClassLoader

BaseDexClassLoader 用于加载各种 dex 中的类。 PathClassLoader、DexClassLoader 都只是在构造函数上对其简单封装而已。

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
    throw new RuntimeException("Stub!");
}

构造参数有四个:dexPath、optimizeDirectory、libraryPath、parent

dexPath:指目标类所在的 dex、apk 或 jar 文件的路径(可以是 sd 卡的路径)。如果要包含多个路径,路径之间必须使用特定的分隔符进行分割,特定的分割符可从 System.getProperty(“path.separtor”) 获得。最终会将 dexPath 路径上的文件 odex 优化到内部位置 optimizedDirectory,再进行加载。

optimizedDirectory:由于 dex 被包含在 apk 或 jar 文件中,因此在加载目标类之前需要先从 apk 或 jar 中解压出 dex 文件,该参数就是指定解压出的 dex 文件存放的路径。这也是对 apk 中 dex 根据平台进行 odex 优化的过程。apk 是一个程序压缩包,里面包含 dex 文件,odex 优化就是把包里的 dex 提取出来,变成 odex 文件,因为你提取出来了,系统第一次启动时就不用去解压程序压缩包的程序,少了一个解压的过程,加快启动速度。为什么说是第一次呢?因为 dex 版本只有第一次会解压执行程序到 data/dalvik-cache(针对 PathClassLoader)或者 optimizedDirectory(针对 DexClassLoader)目录,之后也是直接读取目录下的 dex 文件,所以第二次启动就和正常的差不多了。当然这只是简单的理解,实际的 odex 还有一定的优化作用。ClassLoader 只能加载内部存储路径中的 dex 文件,所以这个路径必须为内部路径。

libraryPath:指目标类中所使用的 c/c++ 库存放的地址。

parcent:指该加载器的父加载器,一般为当前执行类的加载器,例如在 Android 中以 context.getClassLoader() 作为父加载器。

2.4 PathClassLoader

public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
    super(dexPath, null, libraryPath, parcent);
}

可以看出 PathClassLoader 将 optimizedDirectory 置为 null。这时候 optimizedDirectory 会使用 dexPath 的值。

据测试,PathClassLoader 在 dalvik 上只能加载已经安装的 apk,在 art 上则没有这个限制。

在 App 的 Activity 页面中调用 getClassLoader,返回的就是 PathClassLoader 对象,它的父加载器是 BootClassLoader。

2.5 DexClassLoader

DexClassLoader 支持加载包含 classes.dex 的 jar/apk 文件,可以是 sd 卡上的路径。

上面说 dalvik 不能直接识别 jar,DexClassLoader 却可以加载 jar 文件,这难道不矛盾吗?其实在 BaseDexClassLoader 里对“.jar”、“.zip”、“.apk”、“.dex”后缀的文件最后都会生成一个对应的 dex 文件,所以最终处理的还是 dex 文件,而 URLClassLoader 并没有做类似的处理,一般我们都是使用 DexClassLoader 作为动态加载的加载器。

注意:
optimizedDirectory 在 8.1 及以上已经被废弃了,DexClassLoader 调用父类构造方法时也是固定传递 null,所以在 8.1 及以上 DexClassLoader 和 PathClassLoader 没有区别。

2.6 InMemoryDexClassLoader

public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parcent) {
    super(dexBuffers, parcent);
}

InMemoryDexClassLoader 是在 API26 时新增的类加载器,继承自 BaseDexClassLoader。

InMemoryDexClassLoader 的构造函数调用其父类 BaseDexClassLoader 的构造函数,ByteBuffer 数组构造了一个 DexPathList,可以用于内存中的 dex 文件。

2.7 DelegateLastClassLoader

DelegateLastClassLoader 是在 API27 时新增的类加载器,继承自 PathClassLoader。
DelegateLastClassLoader 实行最后查找策略。使用 DelegateLastClassLoader 来加载每个类或资源,使用以下查找顺序:

首先,判断是否已经加载过该类。
然后,搜索此类的类加载器是否加载过该类。
最后,搜索与该类加载器的 dexPath 关联的 dex 文件列表,委托给指定的父对象加载。

三、ClassLoader 的双亲委托模型

双亲委托模型是指先委托父加载器寻找目标类,只有在找不到的情况下才从自己的类路径下查找并加载目标类。

Java Launcher 类中,初始化时会生成一个 ExtClassLoader 和 一个 AppClassLoader,BootstrapClassLoader 作为 ExtClassLoader 的双亲,ExtClassLoader作为 AppClassLoader 的双亲。

加载类时使用 AppClassLoader,AppClassLoader 去询问 ExtClassLoader,ExtClassLoader 去询问 BootstrapClassLoader,如果它们都不处理,那么 AppClassLoader 自己去处理。

由此实现一种优先级,BootstrapClassLoader > ExtClassLoader > AppClassLoader。

更多可见一句话讲清楚双亲委托模型。

3.1 实现

实现就在 ClassLoader 的 loadClass() 方法。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        return c;
}
  1. 判断目标类是否被加载过,加载过了就直接返回。
  2. 委托父加载器去加载,加载成功就直接返回。
  3. 自己去加载。

3.2 好处

  1. 通过第一步,可避免重复加载。
  2. 通过二、三步,使 Java 的基础环境(JDK 类)的加载都由模型最顶端的启动类加载器完成,保证系统行为统一,即所有使用到 JDK 类的代码中,引用到的都是同一个类。

测试:

  1. 自己编写一个 java.lang.String 类,实现一个 toString() 方法,再打印一个 String。能编译通过,但自己的 String 类的 toString() 方法没有被调用。
public class String {
    @Override
    public String toString() {
        return "bbb";
    }
}

public class Main {
    public static void main(String[] args) {
        String s = "aaa";
        System.out.println(s);
    }
}

// 输出
aaa
  1. 自己编写一个 java.lang.String 类,实现一个 print() 方法,再调用它。能编译通过,但执行时会抛找不到方法的异常。
public class String {
    public void print() {
        System.out.println("ccc");
    }
}

public class Main {
    public static void main(String[] args) {
        String s = "aaa";
        s.print();
    }
}

// 输出:
Exception in thread "main" java.lang.NoSuchMethodError: 
	java.lang.String.print()V
		at com.gdeer.classloadertest.Main.main(Main.java:6)
  1. 自己编写一个空的 java.lang.String 类。调用系统 String 的方法,如 String.length(),编译不通过。
public class String {
}

public class Main {
    public static void main(String[] args) {
        String s = "aaa";
        System.out.println(s.length());
    }
}

// 编译错误
错误: 找不到符号

综以上三条测试所述,java.lang.String 类的加载总会使用系统类加载器,系统类加载器会从系统路径加载 java.lang.String。

拓展开来,系统类的加载总会使用系统类加载器,系统类加载器会从系统路径加载系统的类。

  1. 自己编写一个 ClassLoader,强行用 defineClass() 方法加载系统的 java.lang.String。会收到一个java.lang.SecurityException: Prohibited package name: java.lang 异常。

3.3 打破

双亲委托模型只是 JDK 提供的 ClassLoader 类的实现方式,用于 JDK 的系统类加载。在实际开发过程中,我们可以通过自定义 ClassLoader 并重写父类的 loadClass 方法来打破这一机制。

3.4 双亲委托模型在热修复领域的应用

一个 ClassLoader 可以有多个 dex 文件,每个 dex 文件是一个 element,多个 dex 文件排成一个有序数组 dexElements,当找类的时候,会按照顺序遍历 dex 文件,然后从当前遍历的 dex 文件中寻找类,由于双亲委托模型,只要找到就会停止查找并返回,如果找不到就从下一个 dex 文件继续找。只要我们先加载修复好的 dex 文件,那么就不会加载有 bug 的 dex 文件了。

另外,假设 app 中有个类叫做 A,在其内部引用了 B,发布过程中发现 B 中有编写错误,那么想要发布一个新的 B 类,就要阻止 A 类打上 CLASS_ISPREVERIFIED 的标志。

Android中ClassLoader源码解析之真的是你认为的ClassLoader 1.前言 首先,阅读本文章之前,需要了解java中的ClassLoader的基本原理,包括java中的三级ClassLoader机制以及ClassLoader的委托机制,否则下面的内容会不知道在讲什么。虽然Android中的ClassLoader也是遵循其委托机制,但是他没有遵循java的三级ClassLoader机制,而是自己造了一个,修改了java系统的代码,如果将两者混淆的话,在Andro 阅读详情

相关推荐

Android ClassLoader详解

我们知道不管是插件化还是组件化,都是基于系统的ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应

xiangzhihong8的专栏 1万+

ClassLoader源码

ClassLoader 顾名思义就是类加载器 ClassLoader 是一个抽象类 没有父类。

我的博客 837

本地部署ComfyUI:开源AI生图与视频生成完整实战指南

在生成式AI应用快速普及的今天,云端工具常受制于排队、积分和素材管理等问题,而本地化部署正成为开发者与创作者的高效替代方案。其核心思路是基于开源工作流引擎,将图像生成、视频生成与后期处理整合到同一套可离线运行的系统中。ComfyUI作为当前最受关注的节点式工作流平台,凭借灵活的模块组合和强大的自定义能力,降低了从图像模型调用到视频扩展的工程门槛。本文从环境配置与依赖安装入手,系统讲解如何部署ComfyUI、加载Checkpoint模型、跑通文生图基础流程,再通过自定义节点接入视频生成能力,同时介绍API批量

weixin_30985047的博客 564

ClassLoader详解

ClassLoader讲解,以及java与Android的对比。

陈德山 712

别再死记硬背了!用Multisim仿真5分钟搞懂运放电压跟随器到底要不要加反馈电阻

本文通过Multisim仿真实验,深入解析运放电压跟随器中反馈电阻的必要性。实验对比不同阻值对电路性能的影响,揭示反馈电阻在提供偏置电流通路、提升高频稳定性和ESD保护中的关键作用,并给出工程实践中的优化建议,帮助工程师快速掌握运放电路设计要点。

weixin_42641998的博客 371

Docker+vLLM内网离线部署Qwen3 流程

本文介绍了在CentOS 7系统下进行VLLM容器化部署Qwen3-32B大模型的内网离线方案。首先需准备Nvidia显卡驱动、CUDA 12.4和Docker环境。通过联网机器拉取vllm/vllm-openai镜像并导入内网服务器,同时从魔塔社区下载模型文件。部署时使用docker run命令启动容器,配置了GPU资源、内存共享、端口映射等参数,特别指定了模型路径、并行计算、内存利用率等关键参数。该方案实现了大模型在内网环境的安全高效部署,适用于需要离线运行的AI推理场景。

qq_42881308的博客 1036

4. 类的加载器

类的加载器

ZLW的博客 2027

ESP32 Arduino 学习篇(五)TFT_eSPI库

该库有User_Setup.h和 User_Setup_Select.h两个配置文件,支持 ①自定义参数或 ②使用已有配置 驱动TFT屏幕。优点是: 1. 占用空间非常小 2. 动画清晰,充分利用每个像素点 3. 可以非常灵动. 4. 方便进行微调。首先在Libraries里面搜索安装TFT_eSPI库到你的工程文件里面。初始化屏幕, 如果是ST7735,可以往里面传一个参数, 具体用到时再看。缺点是: 1. 容易闪屏 2. 一般不会太复杂 3.需要大量的计算。特别注意: 字库7是仿7段数码屏的样式。

DOF526570的博客 2万+

Android解析ClassLoader(二)Android中的ClassLoader

在上一篇文章我们学习了Java的ClassLoader,很多同学会把Java和Android的ClassLoader搞混,甚至会认为Android中的ClassLoader和Java中的ClassLoader是一样的,这显然是不对的。这一篇文章我们就来学习Android中的ClassLoader,来看看它和Java中的ClassLoader有何不同。

qq_39431405的博客 1238

智能车竞赛新手必看:单车定向组直线竞速与八字慢速实战技巧

本文为智能车竞赛新手提供单车定向组直线竞速与八字慢速的实战技巧,涵盖赛道分析、动力调校、传感器配置等关键环节。通过真实案例和代码示例,详细讲解如何平衡速度与稳定性,帮助选手避免常见失误,提升比赛成绩。特别针对智能车竞赛中的直线竞速和八字慢速项目,提供从硬件选型到临场策略的全方位指导。

weixin_28718793的博客 434

MinerU 系列教程 | 第二课:快速上手 -- 安装与第一次解析

本篇教程作为的第二课,将带你从零完成 MinerU 的环境搭建、模型下载,并成功运行第一次文档解析。上一课我们从宏观视角认识了 MinerU,本课开始正式动手实践。

weixin_43674738的博客 300

安卓高手之路之 ClassLoader

我不喜欢那些泛泛而谈的去讲那些形而上学的道理,更不喜欢记那些既定的东西。靠记忆去弥补思考的人,容易陷入人云亦云的境地,最后必定被记忆所围困,而最终消亡的是创造力。希望这个高手之路系列能够记录我学习安卓的点点滴滴。从而汇成流,聚为江,成为海。         下面就结合代码分析一下ClassLoader这个东西。         安卓应用程序是一个Dalvik虚拟机,加载的是Dex格式的文件。加

daojin505的专栏 211

UGUI合批杀手:Mask/RectMask2D为何必断合批?

颜色缓冲负责记录像素颜色;模板缓冲负责记录“这个像素的模板值”(一个小整数);渲染时,你可以设定:只有模板值为 X 的地方才允许画;或者往某些区域写特定的模板值。类似于:先拿一支隐形笔在纸上画出一块区域,给它记一个编号“1”,后面的涂色动作只允许涂在“编号为1”的区域上。合批的前提:渲染状态不变,连续元素能一口气画完。材质、贴图、混合模式、Stencil、裁剪区域……都得一致。Mask / RectMask2D 的本质:在渲染状态上加规则——“只在某个区域内允许写像素”。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 118

2024年安卓最新深入理解Android中的ClassLoader,阿里测试开发面经

代码真的是重质不重量,质量高的代码,是当前代码界提倡的,当然写出高质量的代码肯定需要一个相当高的专业素养,这需要在日常的代码书写中逐渐去吸收掌握,谁不是每天都在学习呀,目的还不是为了一个,为实现某个功能写出高质量的代码。所以,长征路还长,大家还是好好地做个务实的程序员吧。最后,小编这里有一系列Android提升学习资料,有兴趣的小伙伴们可以来看下哦~网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取。

2401_84563220的博客 841

【Unity】【Pico】【VR开发】为何PICO打包后真机运行闪退

由于并没有开版权验证,而且闪退后也并没有弹框说版权问题,所以还是怀疑环境有什么地方不对。设置步骤,项目配置都没问题,Build也成功,Unity版本是符合要求的2022LTS版本,但是一在真机上运行就闪退。

每日出拳老爷子的博客 3175

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Android进阶(七)Android中的ClassLoader

一、Java中的ClassLoader 1、继承关系 ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。 SecureClassLoader继承了抽象类ClassLoader,但SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。 URLClassLo...

Android学习笔记 1538

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Android的ClassLoader知多少

loadClass基本流程 /** * Loads the class with the specified <a href="#name">binary name</a>. The */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundExcep...

天外来客1009 333

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android ClassLoader加载机制详解

Android ClassLoader机制解析:本文详细介绍了Android中的类加载机制,包括ClassLoader的核心概念、继承体系和工作原理。重点分析了双亲委派模型的实现过程,以及DexPathList和DexElement在类加载中的作用。文章还探讨了Android特有的类加载器(DexClassLoader、PathClassLoader等)与Java的区别,并提供了自定义ClassLoader的示例。通过解析ClassLoader的底层实现,有助于理解插件化、热修复等高级功能的实现原理,为相关

顾林海 2175

Android ClassLoader

Android中ClassLoader的种类: 1 BootClassLoader 和java虚拟机中不同的是BootClassLoader是ClassLoader的内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,是包内可见,我们没法使用,也不能使用动态加载。 2 URLClassLoader 只能用于加载j...

柳亚星的博客 287

Android ClassLoader 小结

简介 Android的ClassLoader和Java的ClassLoader有一些差异: Java中的ClassLoader可以加载jar文件和Class文件(本质是加载Class文件) Android中ClassLoader加载的不再是Class文件,而是dex文件(DVM,ART都是如此) 不过都是遵循双亲模式:有解析请求会先交给父类,如果父类无法解析才会往下传 Java...

vivian的专栏 841
上一篇: 《Android 开发艺术探索》笔记:(4)View 的工作原理
下一篇: 机器学习简介
Gdeer
博客等级 码龄12年 21粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值