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

东方财富网 数据访问接口及字段说明

东方财富网 数据访问接口和字段说明

雨中山草 2万+

ClassLoader详解

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

陈德山 712

AOA(Angle of Arrival,到达角)定位算法及其误差分析的原理和MATLAB仿真_aoa定位算法

如图所示,有基站 S 1 ( x 1 , y 1 , z 1 ) , S 2 ( x 2 , y 2 , z 2 ) S_1(x_1,y_1,z_1),S_2(x_2,y_2,z_2) S1​(x1​,y1​,z1​),S2​(x2​,y2​,z2​),目标 T ( x , y , z ) T(x,y,z) T(x,y,z)。2+(z_1-z_2)^2} step1:两基站间的距离L=(x1−x2)2+(y1​−y2​)2+(z1​−z2​)2。

2401_87555613的博客 1482

瑞芯微RV1126B芯片实战:如何用3Tops NPU打造智能安防摄像头(附配置指南)

本文详细介绍了基于瑞芯微RV1126B芯片构建高性能智能安防摄像头的实战经验。文章深入剖析了其3Tops NPU与独立AI-ISP的协同优势,提供了从硬件选型、开发环境搭建、AI模型部署到系统调优的全流程配置指南,帮助开发者充分发挥芯片潜力,实现高效、稳定的智能视觉应用。

weixin_29281915的博客 443

4. 类的加载器

类的加载器

ZLW的博客 2027

《哥谭神话-工程篇》01.Palantir 平台架构解析-混乱数据如何转化唯一真相源?

Palantir 平台的架构哲学可以概括为将洞察落地为行动,其核心是将“获取唯一真相”能力深度嵌入现有的、复杂的工作流程中,成为提升运营效率和决策质量的倍增器。

897

Android解析ClassLoader(二)Android中的ClassLoader

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

qq_39431405的博客 1238

Unity地形优化实战:LOD、合批与HLOD打造流畅开放世界

在计算机图形学与游戏开发中,渲染优化是保障应用流畅运行的核心技术。其基本原理在于通过算法与策略,在视觉保真度与硬件资源消耗之间寻求最佳平衡,旨在提升帧率、降低功耗。这一技术的核心价值在于,它直接决定了大规模场景(如开放世界游戏)在性能受限平台(尤其是移动设备)上的可行性。具体到地形渲染这一关键应用场景,开发者需要综合运用多层次细节(LOD)技术来动态调整模型精度,并通过Draw Call合批(Batching)与GPU实例化(GPU Instancing)来大幅减少CPU到GPU的通信开销。对于超大规模世界

weixin_28077113的博客 332

抖音中动态生成msToken的方法

抖音中动态生成msToken的方法

sh_moranliunian的博客 2483

安卓高手之路之 ClassLoader

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

daojin505的专栏 211

【回眸】Tessy集成测试软件使用指南(一)新手使用篇

这个专栏的文章前4篇都在记录如何使用Tessy进行单元测试,集成测试需要有一定单元测试基础,且做集成测试之前,需要做好单元测试,否则将会大幅增加软件单元代码纠错的代价。集成测试所花费的时间通常远远超过单元测试。如果直接从单元测试到系统测试,结果往往是不理想的。集成测试可以检测由于单元相互交互而导致的错误,通常由一组专业的测试人员执行。

啤酒鸭的物联网开发记录博客 5278

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

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

2401_84563220的博客 841

Cadence OrCAD Capture 保持元器件编号情况复制元件,复制后元器件编号与复制前不同的问题解决

如下图所示复制时,已经勾选了“Preserve reference on copy”但是复制后的编号却与复制前不一样。本文简单介绍其原因及解决方法。

fydar的博客 3163

VB摄像头监控

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

Android进阶(七)Android中的ClassLoader

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

Android学习笔记 1538

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

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

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

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、付费专栏及课程。

余额充值