Android中类加载机制

时间:2024-03-16 17:04:23

Android 双亲委派机制

Android 中的双亲委派机制是一种类加载机制,用于从多个类加载器中加载类。它允许类加载器委派加载类的请求给其父类加载器,直到找到可以加载该类的类加载器为止。

工作原理:

当一个类需要被加载时,Java 虚拟机 (JVM) 会首先尝试从当前类加载器加载它。如果当前类加载器无法加载该类,它会将加载请求委派给其父类加载器。这个过程一直持续到根类加载器 (BootStrap ClassLoader),它负责加载 Java 核心库中的类。

如果根类加载器也无法加载该类,则会抛出 ClassNotFoundException 异常。

优点:

**隔离性:**不同的类加载器可以加载不同的类,从而实现类之间的隔离性。
**安全性:**可以控制不同类加载器加载类的权限,从而增强安全性。
**灵活性:**允许在运行时动态加载类,提高了应用程序的灵活性。
示例:

// 自定义类加载器
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从指定路径加载类
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
}

// 使用自定义类加载器加载类
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> myClass = myClassLoader.loadClass("com.example.MyClass");
在上面的示例中,MyClassLoader 是一个自定义类加载器,它从指定路径加载类。当 loadClass("com.example.MyClass") 被调用时,JVM 会使用双亲委派机制尝试从当前类加载器和其父类加载器加载该类。如果无法加载,则会使用 MyClassLoader 加载该类。

注意:

双亲委派机制只适用于类加载,不适用于资源加载。
由于双亲委派机制,子类加载器无法加载父类加载器已经加载的类。
子类加载器到根类加载器的路径是什么样的,有哪些类型的加载器,用法区别
子类加载器到根类加载器的路径:

在 Android 中,类加载器的层次结构如下:

**BootStrap ClassLoader (根类加载器):**加载 Java 核心库中的类。
**Extension ClassLoader:**加载 Java 扩展库中的类。
**System ClassLoader:**加载系统类路径 (CLASSPATH) 中的类。
**自定义类加载器:**加载自定义路径中的类。
子类加载器到根类加载器的路径是:

自定义类加载器 -> System ClassLoader -> Extension ClassLoader -> BootStrap ClassLoader
类型的类加载器:

**BootStrap ClassLoader:**由 Java 虚拟机实现,用于加载 Java 核心库中的类。它是最顶层的类加载器。
**Extension ClassLoader:**加载 Java 扩展库中的类,例如 java.ext.dirs 中的类。
**System ClassLoader:**加载系统类路径 (CLASSPATH) 中的类,例如 java.class.path 中的类。
**自定义类加载器:**由开发者创建,用于加载自定义路径中的类。
用法区别:

**BootStrap ClassLoader:**用于加载 Java 核心库中的类,由 JVM 管理,开发者无法直接使用。
**Extension ClassLoader:**用于加载 Java 扩展库中的类,可以扩展 Java 核心库的功能。
**System ClassLoader:**用于加载系统类路径中的类,是 Android 中最常用的类加载器。
**自定义类加载器:**用于加载自定义路径中的类,可以实现类的隔离、安全控制和动态加载等功能。
示例:

// 自定义类加载器
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从指定路径加载类
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
}

// 使用自定义类加载器加载类
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> myClass = myClassLoader.loadClass("com.example.MyClass");
在上面的示例中,MyClassLoader 是一个自定义类加载器,它从指定路径加载类。

PathClassLoader,DexClassLoader是什么类型加载器,和以上加载器类型怎么对应?


PathClassLoader 和 DexClassLoader 都是 自定义类加载器,用于加载自定义路径中的类。它们与上述类加载器类型的对应关系如下:

类加载器类型    PathClassLoader    DexClassLoader
BootStrap ClassLoader    否    否
Extension ClassLoader    否    否
System ClassLoader    否    否
自定义类加载器    是    是
PathClassLoader 用于加载从文件系统路径加载的类,例如 JAR 文件或目录。它使用 Java 反射 API 来加载类。

DexClassLoader 用于加载从 DEX 文件加载的类。DEX 文件是 Android 应用中使用的可执行文件格式。它使用 Android 框架提供的 dalvik.system.DexClassLoader 类来加载类。

示例:

// PathClassLoader
PathClassLoader pathClassLoader = new PathClassLoader("/path/to/classes.jar", ClassLoader.getSystemClassLoader());
Class<?> myClass = pathClassLoader.loadClass("com.example.MyClass");

// DexClassLoader
DexClassLoader dexClassLoader = new DexClassLoader("/path/to/classes.dex", "/path/to/cache", null, ClassLoader.getSystemClassLoader());
Class<?> myClass = dexClassLoader.loadClass("com.example.MyClass");
在上面的示例中,PathClassLoader 从文件系统路径加载类,而 DexClassLoader 从 DEX 文件加载类。

用法区别:

**PathClassLoader:**用于加载从文件系统路径加载的类,例如 JAR 文件或目录。
**DexClassLoader:**用于加载从 DEX 文件加载的类,通常用于加载 Android 应用中的类。
注意:

PathClassLoader 和 DexClassLoader 都是自定义类加载器,可以实现类的隔离、安全控制和动态加载等功能。
PathClassLoader 和 DexClassLoader 都需要指定父类加载器,通常是 ClassLoader.getSystemClassLoader()。


怎么使用DexClassLoader实现热修复?
使用 DexClassLoader 实现热修复的步骤:

创建 DexClassLoader 实例:

创建一个 DexClassLoader 实例,指定 DEX 文件路径、优化后的 DEX 文件输出路径和父类加载器。
父类加载器通常是 ClassLoader.getSystemClassLoader()。
加载修复类:

使用 DexClassLoader 加载修复类。
修复类通常位于 DEX 文件中。
替换原有类:

使用 Java 反射 API 替换原有类。
找到原有类的 Class 对象,然后使用 set() 方法替换它的父类加载器。
触发类加载:

触发类加载,例如调用修复类的静态方法或创建修复类的实例。
这将导致修复类被加载到内存中。
示例代码:

// 创建 DexClassLoader 实例
DexClassLoader dexClassLoader = new DexClassLoader("/path/to/classes.dex", "/path/to/optimized/dex", null, ClassLoader.getSystemClassLoader());

// 加载修复类
Class<?> fixClass = dexClassLoader.loadClass("com.example.FixClass");

// 替换原有类
Class<?> originalClass = Class.forName("com.example.OriginalClass");
Field parentField = ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(originalClass.getClassLoader(), dexClassLoader);

// 触发类加载
fixClass.getMethod("fix").invoke(null);
注意:

使用 DexClassLoader 实现热修复需要谨慎,因为它可能会导致类加载冲突和不稳定性。
确保修复类与原有类具有相同的签名和方法实现。
在生产环境中使用热修复之前,应进行充分的测试和验证。
优点:

**快速修复:**DexClassLoader 可以快速加载和替换修复类,实现快速修复。
**隔离性:**修复类与原有类隔离,不会影响原有类的功能。
缺点:

**类加载冲突:**如果修复类与原有类具有相同的类名,可能会导致类加载冲突。
**不稳定性:**热修复可能会导致应用程序不稳定,例如崩溃或异常行为。
**安全风险:**如果修复类包含恶意代码,可能会对应用程序造成安全风险。


如果存在相同的类名,加载顺序是怎样的?
如果使用 DexClassLoader 加载两个具有相同类名的类,加载顺序取决于以下因素:

**父类加载器:**DexClassLoader 实例的父类加载器。
**DEX 文件加载顺序:**DEX 文件的加载顺序。
加载顺序规则:

**优先加载父类加载器中的类:**如果父类加载器中存在与 DEX 文件中类名相同的类,则优先加载父类加载器中的类。
**按 DEX 文件加载顺序加载:**如果父类加载器中不存在该类,则按 DEX 文件加载顺序加载 DEX 文件中的类。第一个加载的 DEX 文件中的类将被加载。
示例:

假设我们有两个 DEX 文件:dex1.dex 和 dex2.dex,其中都包含一个名为 com.example.MyClass 的类。

如果 dex1.dex 先于 dex2.dex 加载,则 dex1.dex 中的 com.example.MyClass 类将被加载。
如果 dex2.dex 先于 dex1.dex 加载,则 dex2.dex 中的 com.example.MyClass 类将被加载。
注意:

如果两个 DEX 文件中包含具有相同类名的类,并且父类加载器中不存在该类,则加载顺序是不可预测的。
为了避免类加载冲突,建议在不同的 DEX 文件中使用不同的类名。
解决类加载冲突:

如果需要在不同的 DEX 文件中加载具有相同类名的类,可以采用以下方法解决类加载冲突:

**使用不同的类名:**为每个类使用不同的类名。
**使用自定义类加载器:**创建自定义类加载器,并控制类加载顺序和类加载行为。
**使用 Java 模块系统:**使用 Java 模块系统将类组织到模块中,并控制模块之间的依赖关系