Android class loading mechanism to explain by reading the source code

First with a map to see the life cycle of a class

You can see the life cycle of a class to go through several processes: load, connection initialization . Wherein the connector is divided into three steps: verification, preparation, resolution. Sequentially explained

load

In the loading process, the virtual machine is mainly three things

1, is acquired by a fully qualified name of the class definition of such a binary byte stream
2, this byte stream represents static storage structure into a data structure The method of operating area
3, generates a heap memory in java Representative java.lang.Class object of this class, as the class of the various data access entry.

The first step is to obtain a binary byte stream mainly a class, meaning that the class as a stream loaded into memory, the source of the class did not say, can be a jar package, can also be a class file or apk file. This feature enables plug-in is the theoretical basis of technology.

In this second step is obtained after the byte stream, the virtual machine will save the results of static storage class into the method area, stored in a data structure corresponding to the conversion procedure in the method area, so that the static structures are stored in the memory area method.

After the third step is loaded into memory when the class, each class is generated corresponding to a Class object, when we use this class, this is through an inlet Class object is used, such as when we write programs using the new create an object of a class keyword time, but also through the class object of this class to create.

connection

The main points to verify the connection phase, preparation and resolution.

Verify : mainly for grammatical structure class legality verification, validation, type in line with the semantics of the Java language.
Preparation: This stage is the memory allocated to the class of class variables, set the default initial value, such as a static int variable initial value is 0, the initial value of the Boolean variable is false.
Analysis: Find classes, interfaces, fields and methods in the type of symbol reference constant pool, these processes replaced symbolic references referenced directly.

The resolution process may be difficult to understand, about the symbolic references and direct reference to what it means can be temporarily ignored, this process can be understood as the beginning of a virtual machine to be loaded into various types of memory, such as the fields are not spelled out numbers, but by a symbol to represent, in the analysis stage, the virtual machine memory classes, methods, and so on up the unified management.

initialization

Initialization phase really to the stage of java code defined in the class, initialized at this stage would be class variables and blocks of code, such as class variables are initialized, the default initialization of class variables in the preparation stage, to the stage variable explicit assignment, wherein the static code block is performed at this stage.

Initialization not executed immediately, when a class will be actively used to initialize, these types of situations are the following:

1, when creating a new instance of a class (e.g., by reflection or new)
2, when calling static methods of a class
3, when the static field uses a class or interface
4, when initiating a subclass Time

Android Class Loading

The ClassLoader Java class file is loaded, while the Android virtual machine, whether or dvm art can only identify dex file. So in Java ClassLoader does not apply in Android. Android in java.lang.ClassLoaderthis class is also different in Java java.lang.ClassLoader.

Dalvik virtual machine is identified dex files instead of class files, so we dex file is loaded, or contain dex files apk file or jar file.

First look at the structure of FIG Android classloader

As can be seen, PathClassLoader and DexClassLoader inherit BaseDexClassLoader, and that both the loader what difference does it make? The answer is: there is no difference, strictly speaking, is after API26 (inclusive), the two types of loader makes no difference, while in API26 before, there are still some differences, the main difference is that with the Internet, said, PathClassLoader main loading system has been installed apk, but mainly loaded DexClassLoader unmounted APK / JAR / ZIP / DEX . We look at the source code to know

Look at PathClassLoader source

package dalvik.system;

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

Then look at the source code DexClassLoader

package dalvik.system;
import java.io.File;
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

Clearly seen from the source, the only difference is that different optimizedDirectory this parameter, so the next source look BaseDexClassLoader

Let's look at API26 previous BaseDexClassLoader constructor Source

Look API26 (including 26) after the constructor source BaseDexClassLoader

It should now be clear, in API26 before, by passing optimizedDirectory this parameter is different from the meaning of the parameters is generated odex (optimized dex) files stored road path , determines the different loaders of two types, and in API26 later, this parameter has been abandoned, not used, and that the only difference between the parameters are not used, and that the two are not exactly the same as it is. So, since API26 (including 26), PathClassLoader loader and DexClassLoader two types are the same, then why have two of the same class loader it? In fact, this is a Google engineer the outset reserve expansion, generally PathClassLoader is to use Google engineer, and DexClassLoader is for developers to use, so maintenance is relatively good, but the twists and turns, the two returned to the same.

Loading parent class loader

DexClassLoader parent loader (parent) is PathClassLoader

PathClassLoader parent loader (parent) is BootClassLoader

parent and inheritance are two different things

The difference is BootClassLoader and PathClassLoader

BootClassLoader mainly used to load the class defined in the SDK

PathClassLoader mainly used to load an application in class (included write your own class) as plug-in class loader

(And after API26) BaseDexClassLoader the specific source below to see

The constructor of the three parameters explained

dexPath : Road King apk dex file path or file containing dex, zip path (must be absolute path) , a path may be, may be a plurality of paths, separated by a specific delimiter between a plurality of paths, the specific delimiters can System.getProperty ( "path.separtor") is obtained.

LibraryPath : refers to the target class used in C / C ++ stock discharge path , there may be multiple paths between the need to add a separator.

parent : refers to the current class loader parent loader .

We can see BaseDexClassLoader constructor, creates a direct DexPathList object and pass in parameters, and that the main role of this class, what is it, look at its source code:

# dalvik.system.DexPathList

private final Element[] dexElements;

public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    if (definingContext == null) {
        throw new NullPointerException("definingContext == null");
    }

    if (dexPath == null) {
        throw new NullPointerException("dexPath == null");
    }

    if (optimizedDirectory != null) {
        if (!optimizedDirectory.exists())  {
            throw new IllegalArgumentException(
                    "optimizedDirectory doesn't exist: "
                    + optimizedDirectory);
        }
        // 如果文件不是可读可写的也会抛出异常
        if (!(optimizedDirectory.canRead()
                        && optimizedDirectory.canWrite())) {
            throw new IllegalArgumentException(
                    "optimizedDirectory not readable/writable: "
                    + optimizedDirectory);
        }
    }

    this.definingContext = definingContext;
    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    // 通过makeDexElements方法来获取Element数组
    // splitDexPath(dexPath)方法是用来把我们之前按照“:”分隔的路径转为File集合。
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                       suppressedExceptions);
    if (suppressedExceptions.size() > 0) {
        this.dexElementsSuppressedExceptions =
            suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
    } else {
        dexElementsSuppressedExceptions = null;
    }
    this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}

Where the focus of our attention to its member variables dexElements , it is an Element [] array, which is mainly used for the collection of files stored dex , Element is a static inner class of DexPathList. DexPathList constructor 4 parameters. Its construction method can also be seen passing over the classLoade objects and dexPath can not be null , otherwise it throws null pointer exception.

There is a method makeDexElements of functions to acquire a file comprises a collection element dex.

# dalvik.system.DexPathList
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                         ArrayList<IOException> suppressedExceptions) {
    ArrayList<Element> elements = new ArrayList<Element>();

    // 遍历打开所有的文件并且加载直接或者间接包含dex的文件。
    for (File file : files) {
        File zip = null;
        DexFile dex = null;
        String name = file.getName();

        if (file.isDirectory()) {
            // We support directories for looking up resources.
            // This is only useful for running libcore tests.
            // 可以发现它是支持传递目录的,但是说只测试libCore的时候有用
            elements.add(new Element(file, true, null, null));
        } else if (file.isFile()){
            // 如果文件名后缀是.dex,说明是原始dex文件
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    //调用loadDexFile()方法,加载dex文件,获得DexFile对象
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else {
                // dex文件包含在其它文件中
                zip = file;

                try {
                    // 同样调用loadDexFile()方法
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    // 和加载纯dex文件不同的是,会把异常添加到异常集合中
                    /*
                     * 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);
        }

        // 如果zip或者dex二者一直不为null,就把元素添加进来
        // 注意,现在添加进来的zip存在不为null也不包含dex文件的可能。
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, false, zip, dex));
        }
    }

    return elements.toArray(new Element[elements.size()]);
}

As can be seen from the source, dexPath dex either directly transfer path to the file may comprise transmission type dex .zip or .apk file . Can also be seen by the above code, load a file called dex is loadDexFile () method.

# dalvik.system.DexPathList
private static DexFile loadDexFile(File file, File optimizedDirectory)
        throws IOException {
    // 如果缓存存放目录为null就直接创建一个DexFile对象返回
    if (optimizedDirectory == null) {
        return new DexFile(file);
    } else {
        // 根据缓存存放目录和文件名得到一个优化后的缓存文件路径
        String optimizedPath = optimizedPathFor(file, optimizedDirectory);
        // 调用DexFile的loadDex()方法来获取DexFile对象。
        return DexFile.loadDex(file.getPath(), optimizedPath, 0);
    }
}

The method DexFile source loadDex look at ()

# dalvik.system.DexFile 
static public DexFile loadDex(String sourcePathName, String outputPathName,
    int flags) throws IOException {

    /*
     * TODO: we may want to cache previously-opened DexFile objects.
     * The cache would be synchronized with close().  This would help
     * us avoid mapping the same DEX more than once when an app
     * decided to open it multiple times.  In practice this may not
     * be a real issue.
     */
    //loadDex方法内部就是调用了DexFile的一个构造方法
    return new DexFile(sourcePathName, outputPathName, flags);
}

private DexFile(String sourceName, String outputName, int flags) throws IOException {
    if (outputName != null) {
        try {
            String parent = new File(outputName).getParent();
            if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                throw new IllegalArgumentException("Optimized data directory " + parent
                        + " is not owned by the current user. Shared storage cannot protect"
                        + " your application from code injection attacks.");
            }
        } catch (ErrnoException ignored) {
            // assume we'll fail with a more contextual error later
        }
    }

    mCookie = openDexFile(sourceName, outputName, flags);
    mFileName = sourceName;
    guard.open("close");
    //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}

private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
    // Use absolute paths to enable the use of relative paths when testing on host.
    return openDexFileNative(new File(sourceName).getAbsolutePath(),
                             (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                             flags);
}

private static native long openDexFileNative(String sourceName, String outputName, int flags);

As it can be seen in loadDex () method, its role is to load DexFile files, and the files will dex optimized to the corresponding cache directory.

So now to answer this class lower DexPathList Its main role is to store specified contains dex files, native libraries and optimized catalog, encapsulates dex path.

Summarize

In BaseDexClassLoader the object constructor, creates DexPathList object. In the internal DexPathList constructor, by calling a series of methods, to be included directly or indirectly included dex file decompression and cache optimization dex file by the member variables DexPathList Element[] dexElementsto point to this file. This, dex file is saved in the dexElements array.

Let's look at the specific class loading process

Class loader is loaded on demand, that is to say when a clear need to use the class file will be loaded.

Android in the class is loaded using the parents entrust mechanism to load the way, we start to understand what is called the parent delegation mechanism under

When a ClassLoader to load a class, it will be to judge the current class record if it has been loaded the class, if already mounted, the direct access and returns, if it has not been loaded, it will not immediately use their class loader to load, but commissioned look to the parent loader, which has been recursively find the topmost parent parent class loader (BootClassLoader), if found from the cache, directly returns the class object corresponding to this class, if all had not loaded, they are loaded from the parent class to the top sequentially loaded down until loaded into the target class.

Simply put, it's time to load the first time, there is no load too, if you order from the cache before returning from bottom to top, if not, on their own to develop from top to bottom position to load the class , finally handed over to the initiator to load the class .

Such words could describe, it sounds a mess, how to find. It does not matter, following up on the analysis of the specific mechanism of how to achieve both parents delegate from the source. Because load a class, calling loadClass () method, so take a look at the source code of this method, the following is PathClassLoader and DexClassLoader of loadClass () source code, both of which method is the same.

protected 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);
                }
            }
            return c;
    }

The following is a BootClassLoader loadClass () method Source

@Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }

        return clazz;
    }

To interpret at source

The relationship between the above mentioned parent loader is DexClassLoader ----> PathClassLoader ---> BootClassLoader , it will turn to call the loadClass () method of the three classes.

In the beginning, it will first call the loadClass DexClassLoader class () method inside findLoadedClass () method to query whether the class has been DexClassLoader loader loads too, if already mounted, direct return Class object corresponding to the class, if had not yet been loaded, then determine which parent the parent loader (PathClassLoader) if not null, then a direct call parent.loadClass (className, false) method, upwardly from the parent query loader , enters the loadClass PathClassLoader class () the method, the father of the loader will also be called first findLoadedClass () method to determine whether or not already been loaded, if you load too, will directly return to the class object of the class, if the same has not been loaded before, and call parent.loadClass (className, false) method, this time into the BootClassLoader class loadClass () method , first determine whether or BootClassLoader same class loader has been loaded through the class, if already mounted, direct access and returns, if such had not yet been loaded, then direct call BootClassLoader of findClass () method to load the class for the first time ( since B ootClassLoader has no parent loader, so will not call parent.loadClass () method ), whether or not to load, will return, and then by the PathClassLoader class loadClass () method which received the parent class loader is loaded into the parent when the class object loader returned not empty, indicating that the parent loader has been loaded successfully, otherwise explanation fails to load, and that only by PathClassLoader own class loader to load the class, The same results are returned to DexClassLoader, down to the sub-loader calls findClass (className) method (because the child loader in parent.loadClass (className, false) method returns null, it will enter findClass child loader ( className) method), which sequentially loads downwardly.

Then the source process BaseDexClassLoader findClass look at ()

# dalvik.system.BaseDexClassLoader
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    // 调用DexPathList对象的findClass()方法
    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;
}

Obviously you can see the findClass BaseDexClassLoader () method is a direct call findClass DexPathList class () method .

FindClass DexPathList look at the source () method

# dalvik.system.DexPathList
public Class findClass(String name, List<Throwable> suppressed) {
    // 遍历Element
    for (Element element : dexElements) {
        // 获取DexFile,然后调用DexFile对象的loadClassBinaryName()方法来加载Class文件。
        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;
}
# dalvik.system.DexFile
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
    return defineClass(name, loader, mCookie, suppressed);
}

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

We can also see from the above code, in fact DexPathList eventually traverse its own Element [] array, get DexFile object to load the Class file. Before we talk about within DexPathList constructor is calling its makeDexElements () method to create Element [] array, but also mentioned that if the zip file or dex file either is not null, put the elements are added in, and add in the There may not be null nor dex files zip. Can also be seen from the above code, get the Class time nothing with this zip file, the call is DexFile way dex file corresponds to acquire Class .

Source can be seen from the above, when a load from a dex to the class file, the cycle will end, i.e. the back of the document will not be executed to dex. In this case, if there is a class called A.class are simultaneously in two different dex file, the system in the end is what kind A.class a dex file to load it, obviously, depending on which one is the dex file in dexElements array quite early , it will load a dex file class.

From the above it can be summed up

Android is a call to load a Class DexFile of defineClass () method. Instead of calling the ClassLoader defineClass () method . This is unlike Java, after all, Android's virtual machine to load dex files instead of class files.

Parents of the benefits of delegation mechanism

1, to avoid repeated loading, if already loaded once Class, you do not need to load again, but directly read first from the cache.

2, more secure, because the virtual machine that only two classes the same name and is loaded with a class loader is the class of the same class, so this mechanism ensures the system-defined classes will not be replaced.

If you do not use parents trust mode, you can customize a system to replace the String class String class, which will obviously pose a security risk, the use of parents entrusted mode will make the String class system was loaded when the Java virtual machine starts, it You can not customize the String class instead of the String class system. There are only two classes the same name and is loaded with a class loader class, Java virtual machine will think they are the same class.

Sequence of a loaded class Android

Traversal DexPathList of Element [] array, the Element contains DexFile, calling DexFile way to get the Class file, if it acquires the Class, you out of the loop. Otherwise, look for a Class Element in the next.

Think about a problem, why not DexClassLoader the parent incoming BaseDexClassLoader?

The loading process with the class about, our main purpose is to optimize the incoming parent, it can recursively to find, so as not to repeat a class to load, and the system did not use BaseDexClassLoader had to load class, Chuan Chuan BaseDexClassLoader parent and null is the same.

 

This article talked about here, and by the source to understand the class loading mechanism, should be able to deepen the impression of a later article will also point to understand knowledge by reading a variety of source code form. If the article help you, you can focus on my next public number "ape Inn", the article will be updated regularly Yo.

​​​​​​​

 

 

Published 23 original articles · won praise 19 · views 2133

Guess you like

Origin blog.csdn.net/huyinda/article/details/105042686