ClassLoader in Java

1. Inheritance

  • ClassLoader is an abstract class that defines the main functions of a ClassLoader.
  • SecureClassLoader inherits the abstract class ClassLoader, but SecureClassLoader is not the implementation of ClassLoader. SecureClassLoader extends the ClassLoader class and adds permissions to enhance ClassLoader security.
  • URLClassLoader inherits from SecureClassLoader and is used to load classes and resources from JAR files and folders through URl paths.
  • ExtClassLoader and AppClassLoader both inherit from URLClassLoader. They are both internal classes of Launcher, which is an entry application to the Java VIRTUAL machine. Both ExtClassLoader and AppClassLoader are initialized in the Launcher.

ClassLoader in Android

1. Inheritance

  • ClassLoader is an abstract class that defines the main functions of a ClassLoader. BootClassLoader is its inner class.
  • SecureClassLoader inherits the abstract ClassLoader class. SecureClassLoader is not a ClassLoader implementation class, but extends the ClassLoader class to add permissions to enhance ClassLoader security.
  • The URLClassLoader class inherits from SecureClassLoader and is used to load classes and resources from JAR files and folders through URl paths.
  • InMemoryDexClassLoader is a class loader added on Android8.0. It inherits from BaseDexClassLoader and is used to load dex files in the memory.
  • BaseDexClassLoader inherits from ClassLoader and is the concrete implementation of the abstract class ClassLoader. Both PathClassLoader and DexClassLoader inherit from it.

2. Function and classification

(1) Function: The Android VIRTUAL machine runs Dex bytecode (the product generated by combining and optimizing Class files), and the ClassLoader is used to load Dex files.

(2) Classification is divided into system class loaders and custom class loaders. The main system class loaders:

  • BootClassLoader
  • PathClassLoader
  • DexClassLoader

Three, Android ClassLoader source code analysis

/dalvik/system

1, this

(1) Constructor:

protected ClassLoader() {
    this(checkCreateClassLoader(), getSystemClassLoader());
}
protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), parent);
}
Copy the code

The ClassLoader needs to pass in a parent ClassLoader. If the parent ClassLoader does not pass in, create a PathClassLoader using getSystemClassLoader

public static ClassLoader getSystemClassLoader() {
    return SystemClassLoader.loader;
}
  static private class SystemClassLoader {
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path".".");
    String librarySearchPath = System.getProperty("java.library.path"."");
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
Copy the code

(2) loadClass:

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, checkifThe class has already been loaded // > c = findLoadedClass(name);if(c == null) {try {//(2) If not loaded, let the parent loader load firstif(parent ! = null) { c = parent.loadClass(name,false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException thrown ifClass not found // from the non-null parent class loader} // (3) If the parent loader fails to load, it will load itselfif (c == null) {
            // If still not found, then invoke findClass inorder // to find the class. c = findClass(name); }}return c;
}
Copy the code

(1) Except for the top-level class loader, other class loaders have their own parent class loaders. When loading a class, they first determine whether the class has been loaded, and return it directly if it has been loaded. (2) If it has not been loaded, the parent loader will first try to load, and eventually all load requests will be passed to the top loader. (3) When the parent loader finds that the required class is not found and cannot complete the loading request, the child loader will load in the findClass method.

2, BaseDexClassLoader

(1) Structure:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    this(dexPath, librarySearchPath, parent, null, false);
}

public BaseDexClassLoader(String dexPath,
        String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
        boolean isTrusted) {
    super(parent);
    this.sharedLibraryLoaders = sharedLibraryLoaders == null
            ? null
            : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
    if (reporter != null) {
        reportClassLoaderChain();
    }
}
Copy the code
  • DexPath: indicates the path of the APK or JAR file where the target class resides. To contain multiple paths, separate the paths with specific separators. You can use System.getProperty(” path.separtor “) to obtain the specific separators.
  • OptimizedDirectory: decompressed dex file storage path, this path must be an internal storage path, generally use the current application private path: /data/data//… .
  • LibrarySearchPath: The path to the C/C++ inventory used in the target class
  • Parent: parent loader.

DexPathList maintains an array of Elements in which the Element is the Dex file

#DexPathList
 /*package*/ static class Element {
 
    @UnsupportedAppUsage
    private final File path;
    /** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */
    private final Boolean pathIsDirectory;
    @UnsupportedAppUsage
    private final DexFile dexFile;
    private ClassPathURLStreamHandler urlHandler;
    private boolean initialized;
    
    @UnsupportedAppUsage
    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); }... }Copy the code

3, BootClassLoader

The ultimate parent of all ClassLoaders on Android. The Android system uses BootClassLoader to preload common classes at startup.

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();
        }
        returninstance; }... }Copy the code

BootClassLoader access modifiers are default and can only be accessed in the same package

4, PathClassLoader

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

@libcore.api.CorePlatformApi
public PathClassLoader(
        String dexPath, String librarySearchPath, ClassLoader parent,
        ClassLoader[] sharedLibraryLoaders) {
    super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
}
Copy the code
  • Use the PathClassLoader to load system classes and application classes, usually to load the dex files of the installed APK
  • The constructor does not have optimizedDirectory and cannot define the dex file path. The default value of this parameter is /data/dalvik-cache

5, DexClassLoader

public DexClassLoader(String dexPath, String optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    super(dexPath, null, librarySearchPath, parent);
}
Copy the code

DexClassLoader can load dex files and compressed files (apk and JAR files) containing dex. In its parent class BaseDexClassLoader, a corresponding dex file is generated for files with suffixes of “.jar”,”.zip”,”.apk”, and “.dex”.

Android Class loading process

1, BaseDexClassLoader findClass

@Override protected Class<? > findClass(String name) throws ClassNotFoundException { // First, check whether the class is presentin our shared libraries.
    if(sharedLibraryLoaders ! = null) {for (ClassLoader loader : sharedLibraryLoaders) {
            try {
                return loader.loadClass(name);
            } catch (ClassNotFoundException ignored) {
            }
        }
    }
    // 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);
    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;
}
Copy the code

The findClass method of DexPathList is called internally

#DexPathListprivate Element[] dexElements; . public Class<? > findClass(String name, List<Throwable> suppressed) {for(Element element : dexElements) { Class<? > clazz = element.findClass(name, definingContext, suppressed);if(clazz ! = null) {returnclazz; }}if(dexElementsSuppressedExceptions ! = null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); }returnnull; } public Class<? > findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) {returndexFile ! = null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null; }Copy the code

DexPathList contains dexElements, an array of Dexfiles. The process of class loading is to traverse this array and call the loadClassBinaryName method of DexFile, and finally call the native method defineClassNative.

Resources: Android dynamic loading of the ClassLoader details