java classloader and android classloader and android classloader class loading mechanism

There is a certain difference between the java classloader and the android classloader
. The class file loaded by the java classloader
is loaded by the android classloader. The dex file

java classloader


There are several types of class loaders . Each type is responsible for loading different classes. Each is responsible for its own. Their relationship is the inheritance relationship from the parent class. From top to bottom are:

Bootstrap ClassLoader (startup class loader): This class loader is implemented by C++. Responsible for loading Java basic classes, the corresponding loaded files are rt.jar, resources.jar, charsets.jar and class in the %JRE_HOME/lib/ directory.

Extension ClassLoader (standard extension class loader): Inherit URLClassLoader. The corresponding loaded files are jar and class in the %JRE_HOME/lib/ext directory.

App ClassLoader (system class loader): inherits URLClassLoader. Corresponds to all jars and classes in the classpath directory of the loaded application.

Another is our custom ClassLoader , implemented by Java. We can customize the class loader and specify the class file under which path the class loader should load

The class loading mechanism relies on the parent delegation mechanism .

At first, we used classloader.loadclass(classname) loadclass to load a class. Let’s take a look at the source code of this method.

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
protected synchronized 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);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

At the beginning of the loadClass method of this classloader, call findLoadedClass(name); the first step is to check whether it has been loaded.
If the parent.loadClass(name, false) of the parent class is not called recursively, continue to call findLoadedClass(name) in the loadClass of the parent class; check whether it has been loaded, and return the
class instance if the parent class has been loaded. If not, continue with the loadClass of the parent class.
Until there is no parent class, it will call
c = findBootstrapClassOrNull(name); that is, use the bootstrap class loader as the parent class loader. If the parent class launcher has not been loaded, load it. If it is judged that it is not in the scope of its own loading, call c = findClass(name); to be loaded by the subclass. Subclass judgment is not loaded by itself, and then handed over to him for subclass loading. Until the findClass of the last subclass is loaded.

It means that when a class loader wants to load a class, first check to see if it has loaded it, and if not, it will continue to ask whether anyone has loaded it until it asks its ancestor class loader If someone has loaded this inquiry process, then the inquiry process is over. This class will not be loaded again, it has been loaded. Then if everyone has not loaded it. The ancestor class will judge whether it is the class he is responsible for loading. Yes, let him load it. If it is not i who is responsible, leave the matter to his subclass, whether he is responsible. If it is, just load it, instead of continuing to subclass. In this way, it goes down to the bottom subclass to load, if no one is responsible for loading. will throw an exception.

The advantage of parental delegation is
① avoiding repeated loading. When the parent container has already been loaded, there is no need to load it again, preventing the same .class from being loaded. Ask the superior whether the .class has been loaded through delegation. If it has been loaded, it does not need to be reloaded. Guaranteed data security

②Security considerations, to prevent the core class API from being tampered with, through the way of entrusting, to ensure that the core. The same .class is not the same Class object loaded by the device. Only the same class file loaded by the same class loader is the same object. This ensures the execution security of the Class.

Let's imagine that if we don't use this delegation mode, we can use a custom String to dynamically replace the type defined in the java core api at any time, which will have a very large security risk, and the parental delegation method can be Avoid this situation, because String has been loaded by the bootstrap class loader (Bootstrcp ClassLoader) at startup, so the user-defined ClassLoader can never load a String written by itself

Why customize a class loader, how to customize a
custom class loader is to load the specified class file path, the existing class loader above is only responsible for the class range it loads.
How to customize it?

First look at the loadClass method of ClassLoader in the Android5.1 source code

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);
        if (clazz == null) {
            ClassNotFoundException suppressed = null;
            try {
                //先让父类加载器加载
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }
            //当所有父类节点的类加载器都没有找到该类时,当前加载器调用findClass方法加载。
            if (clazz == null) {
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }

That is, if all the class loaders above are not loaded and they are not responsible, then the class loader loads the calling method by itself, which is the findClass method.
If we only need to inherit classloader and then rewrite findClass, it will be fine.

package com.lordx.sprintbootdemo.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * 自定义ClassLoader
 * 功能:可自定义class文件的扫描路径
 */
// 继承ClassLoader,获取基础功能
public class TestClassLoader extends ClassLoader {

    // 自定义的class扫描路径
    private String classPath;

    public TestClassLoader(String classPath) {
        this.classPath = classPath;
    }

    // 覆写ClassLoader的findClass方法
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // getDate方法会根据自定义的路径扫描class,并返回class的字节
        byte[] classData = getDate(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            // 生成class实例
            return defineClass(name, classData, 0, classData.length);
        }
    }


    private byte[] getDate(String name) {
        // 拼接目标class文件路径
        String path = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
        try {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while ((num = is.read(buffer)) != -1) {
                stream.write(buffer, 0 ,num);
            }
            return stream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Use a custom class loader

package com.lordx.sprintbootdemo.classloader;

public class MyClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        // 自定义class类路径
        String classPath = "/Users/zhiminxu/developer/classloader";
        // 自定义的类加载器实现:TestClassLoader
        TestClassLoader testClassLoader = new TestClassLoader(classPath);
        // 通过自定义类加载器加载
        Class<?> object = testClassLoader.loadClass("ClassLoaderTest");
        // 这里的打印应该是我们自定义的类加载器:TestClassLoader
        System.out.println(object.getClassLoader());
    }
}

Android class loader

BootClassLoader , this BootClassLoader is used to load the classes in the sdk framewrok layer system layer, note that it is only the framewrok layer system class. The classes in the library that the project depends on are not considered system classes, so they are not loaded by BootClassLoader, but by PathClassLoader. For example,
insert image description herethe classes in the androidx library are not system sdk classes, but dependent library classes, which are loaded with PathClassLoader.

PathCLassLoader , a subclass of BaseDexClassLoader, is the class loader in the entire program, which is equivalent to AppClassLoader in java,
that is to say, all the classes in our entire project are loaded with PathCLassLoader except for the system classes that are loaded with BootClassLoader, that is to say , according to the parent delegation. When PathCLassLoader loads a class, first determine whether BootClassLoader has been loaded. If not, it will load it by itself, that is, PathCLassLoader will load it by itself.
Note that when we use a class in a project, we don't use CLassLoader .loadClass to load a class, and then reflect to call the information of this class. In fact, internally, the system has already implemented it for us with PathCLassLoader loadClass.

DexClassLoader Subclass of BaseDexClassLoader, equivalent to Java's CustomClassLoader.

The difference between PathClassLoader and DexClassLoader before android 8.0

The constructors of BaseDexClassLoader PathClassLoader and DexClassLoader are

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

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

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

dexPath: specifies the address of the dex file, multiple addresses can be separated by ":"
optimizedDirectory: specifies the storage path of the odex file after JIT optimization of dex libraryPath
: dynamic library path (will be added to the app dynamic library search path list ) can be null
parent: specify the parent class loader to ensure the parent delegation mechanism so that each class is only loaded once.

It can be seen that the difference between PathClassLoader and DexClassLoader is that the optimizedDirectory parameter is added to the constructor.
The optimizedDirectory in PathClassLoader is null, and new File (optimizedDirectory) is needed in DexClassLoader to store dex is actually odex (because it will be compiled and optimized into an odex file after JIT)

The dex file after apk installation is stored in the /data/dalvik-cache directory by default, and PathClassLoader does not have this optimizedDirectory file because the default value of PathClassLoader’s optimizedDirectory is /data/dalvik-cache, so PathClassLoader can only Load the internal dex.

And DexClassLoader needs to specify the optimizedDirectory to create this file, so that the external dex has a place to store even the compiled odex file after JIT, and your DexClassLoader searches for dex resources from your optimizedDirectory.

Then after android 8.0, although the constructor of DexClassLoader still has this optimizedDirectory, it doesn’t matter what your parameter is, it directly passes in null

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

So after android 8.0, there is no difference between DexClassLoader and PathClassLoader.

Note that the optimizedDirectory path should not be written in a random path, it is best to put it in a private directory

context.getCashDir().getAbsolutePath();

Android class loader class loading mechanism

The Android class loader also adopts the parental delegation mechanism. When we call classloader.loadClass, we first check which class loader is needed according to the parental delegation
to findclass

 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);//1
                } else {
                    c = findBootstrapClassOrNull(name);//2
                }
            }catch (ClassNotFoundException e){
                
            }
            if (c == null) {
                c = findClass(name);//3
            }

        }
        return c;

    }
@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //在自己的成员变量DexPathList中寻找 ,返回 要查找的类 ,找不到抛异常
        //也就是说 这个classLoader 负责加载的类 都是在这个 dexpathlist中
        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;
    }

findClass method of DexPathList

public Class findClass(String name, List<Throwable> suppressed) {
        //循环便利成员变量dexElements,
        //查找所有负责每个elements.dexFile  调用DexFile.loadClassBinaryName 去根据类名查找要找的class
        //如果找不到就返回null
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

From the above code we can see that,

Each classloader object has its own responsibility to load the corresponding class.
Inside the classloader, there is an object dexpathlist of the member Dexpathlist class. Dexpthlist has an array of Element[] elements, and there are n Elements inside. The Element class has a variable dexFile, and this dexFile is Represents each dex file.

So classloader findclass to find a class is to find it in the elements array in the dexpathlist object

When storing data at the beginning, the classloader will call the makeElements() method in Dexpathlist to turn the dex file into an Element class, and multiple dex files List dex will become an Element[] elements array.

//android 7
#DexPathList.java   makeElements
private static Element[] makeElements(List<File> files, File optimizedDirectory,
                                          List<IOException> suppressedExceptions,
                                          boolean ignoreDexFiles,
                                          ClassLoader loader) {
        Element[] elements = new Element[files.size()];
        int elementsPos = 0;
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File zip = null;
            File dir = new File("");
            DexFile dex = null;
            String path = file.getPath();
            String name = file.getName();

            if (path.contains(zipSeparator)) {
                String split[] = path.split(zipSeparator, 2);
                zip = new File(split[0]);
                dir = new File(split[1]);
            } else if (file.isDirectory()) {
                // We support directories for looking up resources and native libraries.
                // Looking up resources in directories is useful for running libcore tests.
                elements[elementsPos++] = new Element(file, true, null, null);
            } else if (file.isFile()) {
                if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory, loader, elements);
                    } catch (IOException suppressed) {
                        System.logE("Unable to load dex file: " + file, suppressed);
                        suppressedExceptions.add(suppressed);
                    }
                } else {
                    zip = file;

                    if (!ignoreDexFiles) {
                        try {
                            dex = loadDexFile(file, optimizedDirectory, loader, elements);
                        } catch (IOException suppressed) {
                            /*
                             * IOException might get thrown "legitimately" by the DexFile constructor if
                             * the zip file turns out to be resource-only (that is, no classes.dex file
                             * in it).
                             * Let dex == null and hang on to the exception to add to the tea-leaves for
                             * when findClass returns null.
                             */
                            suppressedExceptions.add(suppressed);
                        }
                    }
                }
            } else {
                System.logW("ClassLoader referenced unknown path: " + file);
            }

            if ((zip != null) || (dex != null)) {
                elements[elementsPos++] = new Element(dir, false, zip, dex);
            }
        }
        if (elementsPos != elements.length) {
            elements = Arrays.copyOf(elements, elementsPos);
        }
        return elements;
    }

So
the actual process when classloader findclass loads classes is:

classloader.loadclass> classloader findclass >dexpathlist finclass > traverse the dexFilie of each element in the elements array to call >
dexFile loadclassBinaryName()>defindclass()>defindNative(), it is a Native method, and finally go to the Native layer to find it.

Guess you like

Origin blog.csdn.net/weixin_43836998/article/details/126288460