了解 Android 类加载器的工作原理,DexPathList 在类加载过程中的作用

移动开发
Android的类加载器系统基于JVM的类加载器模型,但有一些特定的调整和优化,以适应Android平台的需要。

类加载器

在Android中,类加载器(ClassLoader)是一个重要的组件,负责在运行时动态加载JVM和Android类库。Android的类加载器系统基于JVM的类加载器模型,但有一些特定的调整和优化,以适应Android平台的需要。

(1) Bootstrap ClassLoader:

  • 这是最顶层的类加载器,由JVM实现。
  • 主要加载Java和Android核心类库。
  • 通常通过null作为父加载器。

(2) PathClassLoader(或DexClassLoader):

  • Android特有的类加载器,用于从APK文件、DEX文件或JAR/ZIP文件中加载类。
  • PathClassLoader是Android应用默认的类加载器,用于加载应用的类和资源。

DexClassLoader是PathClassLoader的一个子类,提供了从指定的路径加载DEX文件的能力,动态加载插件或模块化场景常用加载器。

//DexClassLoader.java
package dalvik.system;

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
    
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

//PathClassLoader
package dalvik.system;

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);
    }
}

可以发现PathClassLoader和DexClassLoader源码很简单,只包含了一个构造函数,去调用父类BaseDexClassLoader(所有的工作都应该是在BaseDexClassLoader里完成的了)。而这两个加载器不同的是PathClassLoader的构造中少了optimizedDirectory这个参数,原因是PathClassLoader是加载/data/app中的apk,也就是系统中的apk,而这部分的apk都会解压释放dex到指定的目录中,这个操作由系统完成,不需要单独传入路径,而DexClassLoader传入,用来缓存需要加载的dex文件,并创建一个DexFile对象,如果为null,会直接使用dex文件原有路径创建DexFile(这个参数已经弃用,自API26起无效)。

(3) System ClassLoader(或AppClassLoader):

  • Android系统的应用类加载器,继承自URLClassLoader。
  • 用于加载Android系统的类和应用的类。
  • 在Android中不直接引用System ClassLoader或AppClassLoader,通过ClassLoader.getSystemClassLoader()获取。

(4) 自定义ClassLoader:

  • 可以继承ClassLoader类或其子类(如DexClassLoader)来创建自定义的类加载器。
  • 自定义类加载器可以用于加载网络上的类、从数据库加载加密的类、或者实现更复杂的类加载逻辑。

类加载器的主要用途:

  • 动态加载和执行代码,如插件化开发、热更新等。
  • 加载和执行不同来源的代码,如从网络下载的JAR包或DEX文件。
  • 隔离不同来源的代码,防止类冲突和安全问题。

注意:滥用类加载器可能导致内存泄漏和性能问题。在使用类加载器时,应该仔细考虑其生命周期和资源管理。

DexPathList

DexPathList是DexClassLoader和BaseDexClassLoader等类加载器用于处理DEX文件路径的一个内部类。当使用DexClassLoader或BaseDexClassLoader加载DEX文件时,DexPathList起到了关键的作用。

(1) 作用:DexPathList负责管理和维护DEX文件的路径信息,使类加载器能够正确地找到并加载DEX文件中的类。

(2) 构造:DexPathList在DexClassLoader或BaseDexClassLoader的构造函数中被创建。构造DexPathList时,需要提供DEX文件的路径、优化目录、库路径以及父类加载器等参数。

public class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;
    
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class 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;
    }
}

(3) 成员变量:DexPathList有一个私有的final成员变量dexElements,是一个Element数组,包含了所有DEX文件的Element对象,每个Element对象对应一个DEX文件。

private final Element[] dexElements;

public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ...
    this.definingContext = definingContext;
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
    ...
}

(4) 加载DEX文件:在DexPathList的构造函数中,会调用makeDexElements()方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements数组中。

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
    // 1.创建Element集合
    ArrayList<Element> elements = new ArrayList<Element>();
    // 2.遍历所有dex文件(也可能是jar、apk或zip文件)
    for (File file : files) {
        ZipFile zip = null;
        DexFile dex = null;
        String name = file.getName();
        ...
        // 如果是dex文件
        if (name.endsWith(DEX_SUFFIX)) {
            dex = loadDexFile(file, optimizedDirectory);

        // 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别)
        } else {
            zip = file;
            dex = loadDexFile(file, optimizedDirectory);
        }
        ...
        // 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, false, zip, dex));
        }
    }
    // 4.将Element集合转成Element数组返回
    return elements.toArray(new Element[elements.size()]);
}

(5) 加载类:当类加载器需要加载一个类时,会通过DexPathList的loadClass()方法来实现。这个方法会遍历dexElements数组中的每个Element对象,并尝试从对应的DEX文件中加载类。一旦找到需要加载的类,就会返回该类的Class对象。

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        // 遍历出一个dex文件
        DexFile dex = element.dexFile;

        if (dex != null) {
            // 在dex文件中查找类名与name相同的类
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

(6) 优化:为了提高性能,DexPathList还支持DEX文件的优化。在加载DEX文件时,可以将DEX文件优化到指定的目录中,以减少内存占用和提高加载速度。

责任编辑:赵宁宁 来源: 沐雨花飞蝶
相关推荐

2012-02-14 13:39:57

Java

2012-02-09 10:31:17

Java

2021-07-05 06:51:43

Java机制类加载器

2024-03-12 07:44:53

JVM双亲委托机制类加载器

2019-12-09 15:08:30

JavaTomcatWeb

2024-03-08 08:26:25

类的加载Class文件Java

2012-11-06 10:19:18

Java自定义加载Java类

2010-03-16 14:58:15

Java类加载器

2021-01-06 09:01:05

javaclass

2023-05-10 11:07:18

2024-04-09 08:41:41

JVM类加载Java

2019-07-24 08:34:35

Java对象数据结构

2009-08-24 11:36:27

CLR加载过程

2020-10-26 11:20:04

jvm类加载Java

2020-09-30 08:26:33

Spring Boot

2009-02-03 09:42:53

JAVA类JVM指令forName方法

2021-07-28 10:08:19

类加载代码块面试

2011-02-25 09:23:00

Java类加载器

2024-03-28 12:32:18

JVM类加载构造器

2021-05-08 09:02:19

Java加载器
点赞
收藏

51CTO技术栈公众号