Android中的ClassLoader

77 阅读5分钟

ClassLoader的类型

Java 中的 ClassLoader 加载的是 class 文件,但是 Android 中加载的是 dex 文件。在 AndroidStudio 中打开的 ClassLoader.java,依次选择 View > Tools Window > Hierarchy,可以调出继承关系图,如下图所示:

image.png

里面有很多个 ClassLoader,其中几个比较重要的 ClassLoader 分别是:BootClassLoader、PathClassLoader 和 DexClassLoader。

  1. BootClassLoader,用于加载 Android Framework 层的代码。BootClassLoader 定义在 ClassLoader.java中,代码如下(本文源码基于 Android SDK 30 ):
class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }

    ...
} 

注意 BootClassLoader 类前面没有用 public 修饰,所以只有在同一个包中才能访问,在应用程序中是无法直接调用的。

  1. PathClassLoader,用于加载应用程序中的 dex。应用的 apk 安装后会存储在系统的 data/app/ 目录下,启动该应用时,系统会去 data/app/ 目录下找到相应的 apk 加载到内存中,这个加载动作就是 PathClassLoader 完成的。PathClassLoader 只能加载系统中已经安装的 apk,对应到插件化技术中就是加载宿主 apk。其代码如下:
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);
    }
}

运行如下代码:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ClassLoader classLoader1 = getClassLoader();
        Log.d("MainActivity", "classloader1:" + classLoader1);

        ClassLoader classLoader2 = Activity.class.getClassLoader();
        Log.d("MainActivity", "classloader2:" + classLoader2);
    }
}

打印结果是,classLoader1 是 PathClassLoader;classLoader2 是 BootClassLoader。

  1. DexClassLoader,允许 app 在运行期间加载外部的 dex,也可以加载包含 dex 的压缩文件(jar、zip、apk 等),其代码如下:
public class DexClassLoader extends BaseDexClassLoader {

    // 从 API 26 开始,参数 optimizedDirectory 没有作用了
    public DexClassLoader(String dexPath, String optimizedDirectory,
                          String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader 与 DexClassLoader 有什么区别呢?从代码来看唯一的区别是 DexClassLoader 需要自己传 optimizedDirectory(优化路径),PathClassLoader 不需要。但是上面的代码中,DexClassLoader 中调用父类的构造方法传的 optimizedDirectory 是 null,事实上从 API 26 开始,参数 optimizedDirectory 已经废弃,没有作用了,所以从 API 26 开始,PathClassLoader 与 DexClassLoader 没有区别了。

ClassLoader的继承关系

运行一个应用程序需要用到几种类型的类加载器呢?运行下面的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while(classLoader != null){
            Log.d("TAG", classLoader.toString());
            classLoader = classLoader.getParent();
        }
    }
}

打印如下:

2024-04-02 20:45:28.247 3255-3255 TAG com.example.test D dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk"],nativeLibraryDirectories=[/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/lib/x86, /data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]] 2024-04-02 20:45:28.248 3255-3255 TAG com.example.test D java.lang.BootClassLoader@85a3be0

可以看到两种类加载器,一种是 PathClassLoader,另一种是 BootClassLoader。DexPathList 中包含了 apk 的路径,其中 /data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test- hYnsAnZzDkTxi8o73f_kmg==/base.apk 就是应用安装在手机上的位置。DexPathList 是在 BaseDexClassLoader 的构造方法中创建的,里面存储了 dex 相关文件的路径,在 ClassLoader 执行双亲委托机制进行查找流程时会从 DexPathList 中查找。

这几种常用的ClassLoader的继承关系如下:

classDiagram
ClassLoader <|-- BootClassLoader
ClassLoader <|-- BaseDexClassLoader
BaseDexClassLoader <|-- PathClassLoader
BaseDexClassLoader <|-- DexClassLoaser

ClassLoader的加载过程

Android 的 ClassLoader 遵循双亲委托机制,所谓双亲委托机制是指加载 dex 文件的过程中,首先判断自己有没有加载过,如果没有,委托父加载器(注意这里父加载器是指 ClassLoader.getParent() 拿到的父加载器)去加载,依次递归,如果父加载器可以完成加载任务,就成功返回;只有父加载器无法完成此加载任务或者没有父加载器时,才自己去加载。

ClassLoader 的加载方法为 loadClass() 方法,这个方法被定义在抽象类 ClassLoader 中,代码如下:

public abstract class ClassLoader {

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name); // 注释 1
        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) { // 注释 2
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        return c;
    }

    private Class<?> findBootstrapClassOrNull(String name) {
        return null;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

其中注释 1 处检查传入的类是否已经加载过,如果已经加载过就直接返回该类。如果没有加载过,判断父加载器是否为 null,如果不为 null,调用父加载器的 loadClass() 方法;如果为 null,调用 findBootstrapClassOrNull() 方法来加载,这个方法会直接返回 null。如果注释 2 处代码成立,说明向上委托流程没有检查出类已经被加载,就会执行 findClass() 方法进行查找,ClassLoader 中的 findClass() 方法直接抛出了异常,其子类 BaseDexClassLoader 对该方法进行了实现:

public class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath,
                              String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
                              boolean isTrusted) {
        super(parent);
        ...
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
        ...
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        ...
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions); // 注释 1
        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;
    }
}

在 BaseDexClassLoader 的构造方法中新建了 DexPathList,在注释 1 处调用了 DexPathList 的 findClass() 方法:

public final class DexPathList {

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

}

里面遍历 Element 数组 dexElements,然后调用了 Element 的 findClass() 方法,Element 是 DexPathList 的静态内部类:

public final class DexPathList {

    /*package*/ static class Element {
        private final File path;
        private final DexFile dexFile;
        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

        public Element(DexFile dexFile, File dexZipPath) {
            if (dexFile == null && dexZipPath == null) {
                throw new NullPointerException("Either dexFile or path must be non-null");
            }
            this.dexFile = dexFile;
            this.path = dexZipPath;
            // Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
            this.pathIsDirectory = (path == null) ? null : path.isDirectory();
        }

        public Element(DexFile dexFile) {
            this(dexFile, null);
        }

        public Element(File path) {
            this(null, path);
        }

        ...

        public Class<?> findClass(String name, ClassLoader definingContext,
                                  List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }
    }
}

从 Element 的构造函数可以看到,其内部封装了 DexFile,DexFile 是用来加载 dex 的,findClass() 方法中调用了 DexFile 的 loadClassBinaryName() 方法:

public final class DexFile {

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
} 

接着调用了 defineClass() 方法,里面调用了 defineClassNative() 方法,这个方法是 Native 方法,就不再往下分析了。