java classloader y android classloader y mecanismo de carga de clases de android classloader

Existe una cierta diferencia entre el cargador de clases de Java y el cargador de clases de Android
. El archivo de clase cargado por el cargador de clases de Java
es cargado por el cargador de clases de Android. El archivo dex

cargador de clases java


Hay varios tipos de cargadores de clases . Cada tipo es responsable de cargar diferentes clases. Cada uno es responsable de lo suyo. Su relación es la relación de herencia de la clase padre. De arriba a abajo son:

Bootstrap ClassLoader (cargador de clases de inicio): este cargador de clases está implementado por C++. Responsable de cargar las clases básicas de Java, los archivos cargados correspondientes son rt.jar, resources.jar, charsets.jar y class en el directorio %JRE_HOME/lib/.

Extensión ClassLoader (cargador de clases de extensión estándar): Inherit URLClassLoader. Los archivos cargados correspondientes son jar y class en el directorio %JRE_HOME/lib/ext.

App ClassLoader (cargador de clases del sistema): hereda URLClassLoader. Corresponde a todos los archivos jar y clases en el directorio classpath de la aplicación cargada.

Otro es nuestro ClassLoader personalizado , implementado por Java. Podemos personalizar el cargador de clases y especificar el archivo de clase en el que la ruta debe cargar el cargador de clases

El mecanismo de carga de clases se basa en el mecanismo de delegación principal .

Al principio, usamos classloader.loadclass(classname) loadclass para cargar una clase.Veamos el código fuente de este método.

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

Al comienzo del método loadClass de este cargador de clases, llame a findLoadedClass(name); el primer paso es comprobar si se ha cargado.
Si parent.loadClass(name, false) de la clase principal no se llama recursivamente, continúe llamando a findLoadedClass(name) en loadClass de la clase principal, verifique si se ha cargado y devuelva la instancia de clase si la clase
principal ha sido cargado. De lo contrario, continúe con la clase de carga de la clase principal.
Hasta que no haya una clase principal, llamará a
c = findBootstrapClassOrNull(name); es decir, usará el cargador de clases de arranque como cargador de clases principal. Si el iniciador de la clase principal no se ha cargado, cárguelo. Si se considera que no está dentro del alcance de su propia carga, llame a c = findClass(name); para que lo cargue la subclase. El juicio de la subclase no se carga por sí mismo y luego se le entrega para la carga de la subclase. Hasta que se cargue findClass de la última subclase.

Significa que cuando un cargador de clases quiere cargar una clase, primero verifica si la ha cargado, y si no, continuará preguntando si alguien la ha cargado hasta que le pregunte a su cargador de clases antepasado si alguien ha cargado esta consulta. proceso, entonces el proceso de consulta ha terminado. Esta clase no se volverá a cargar, se ha cargado. Luego si todos no lo han cargado. La clase antecesora juzgará si es la clase de la que es responsable de cargar. Sí, que lo cargue. Si no soy yo el responsable, deja el asunto a su subclase, si él es el responsable. Si es así, simplemente cárguelo, en lugar de continuar con la subclase. De esta manera, baja a la subclase inferior para cargar, si nadie es responsable de cargar. lanzará una excepción.

La ventaja de la delegación de padres es
① evitar la carga repetida. Cuando el contenedor principal ya se ha cargado, no es necesario volver a cargarlo, lo que evita que se cargue la misma .clase. Preguntar al superior si se ha cargado el .class por delegación, si se ha cargado no hace falta volver a cargarlo. Seguridad de datos garantizada

②Consideraciones de seguridad, para evitar que la API de clase central sea manipulada, a través de la forma de confiar, para garantizar que el núcleo.La misma clase no es el mismo objeto de clase cargado por el dispositivo. Solo el mismo archivo de clase cargado por el mismo cargador de clase es el mismo objeto. Esto garantiza la seguridad de ejecución de la Clase.

Imaginemos que si no usamos este modo de delegación, podemos usar una cadena personalizada para reemplazar dinámicamente el tipo definido en la API principal de Java en cualquier momento, lo que tendrá un riesgo de seguridad muy grande, y el método de delegación parental puede ser Evite esta situación, porque el cargador de clases de arranque (Bootstrcp ClassLoader) ha cargado String al inicio, por lo que el ClassLoader definido por el usuario nunca puede cargar un String escrito por sí mismo.

Por qué personalizar un cargador de clases, cómo personalizar un
cargador de clases personalizado es cargar la ruta del archivo de clase especificado, el cargador de clases existente anterior solo es responsable del rango de clases que carga.
¿Cómo personalizarlo?

Primero observe el método loadClass de ClassLoader en el código fuente de Android5.1

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

Es decir, si todos los cargadores de clases anteriores no están cargados y no son responsables, entonces el cargador de clases carga el método de llamada por sí mismo, que es el método findClass.Si
solo necesitamos heredar classloader y luego reescribir findClass, será bien.

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

Usar un cargador de clases personalizado

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

cargador de clases de Android

BootClassLoader , este BootClassLoader se usa para cargar las clases en la capa del sistema de capa de marco de sdk, tenga en cuenta que es solo la clase del sistema de capa de marco de trabajo. Las clases de la biblioteca de las que depende el proyecto no se consideran clases del sistema, por lo que BootClassLoader no las carga, sino PathClassLoader. Por ejemplo,
inserte la descripción de la imagen aquílas clases de la biblioteca androidx no son clases SDK del sistema, sino clases de biblioteca dependientes, que se cargan con PathClassLoader.

PathCLassLoader , una subclase de BaseDexClassLoader, es el cargador de clases en todo el programa, lo que equivale a AppClassLoader en java, es
decir, todas las clases de todo nuestro proyecto se cargan con PathCLassLoader excepto las clases del sistema que se cargan con BootClassLoader. , es decir , según la delegación de padres. Cuando PathCLassLoader carga una clase, primero determine si BootClassLoader ha sido cargado, si no, lo cargará solo, es decir, PathCLassLoader lo cargará solo.
Tenga en cuenta que cuando usamos una clase en un proyecto, no usamos CLassLoader .loadClass para cargar una clase y luego reflejamos para llamar la información de esta clase. De hecho, internamente, el sistema ya nos lo ha implementado con PathCLassLoader loadClass.

DexClassLoader Subclase de BaseDexClassLoader, equivalente a CustomClassLoader de Java.

La diferencia entre PathClassLoader y DexClassLoader antes de Android 8.0

Los constructores de BaseDexClassLoader PathClassLoader y DexClassLoader son

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: especifica la dirección del archivo dex, varias direcciones pueden estar separadas por ":"
.
list ) puede ser un
padre nulo: especifique el cargador de clases principal para garantizar el mecanismo de delegación principal para que cada clase solo se cargue una vez.

Se puede ver que la diferencia entre PathClassLoader y DexClassLoader es que el parámetro optimizadoDirectorio se agrega al constructor.
El directorio optimizado en PathClassLoader es nulo, y se necesita un nuevo archivo (directorio optimizado) en DexClassLoader para almacenar dex que en realidad es odex (porque se compilará y optimizará en un archivo odex después de JIT)

El archivo dex después de la instalación de apk se almacena en el directorio /data/dalvik-cache de forma predeterminada, y PathClassLoader no tiene este archivo de directorio optimizado porque el valor predeterminado del directorio optimizado de PathClassLoader es /data/dalvik-cache, por lo que PathClassLoader solo puede cargar el directorio interno. dex.

Y DexClassLoader necesita especificar el directorio optimizado para crear este archivo, de modo que el dex externo tenga un lugar para almacenar incluso el archivo odex compilado después de JIT, y su DexClassLoader busca recursos de dex de su directorio optimizado.

Luego, después de Android 8.0, aunque el constructor de DexClassLoader todavía tiene este directorio optimizado, no importa cuál sea su parámetro, pasa directamente a nulo

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

Entonces, después de Android 8.0, no hay diferencia entre DexClassLoader y PathClassLoader.

Tenga en cuenta que la ruta del directorio optimizado no debe escribirse en una ruta aleatoria, es mejor colocarla en un directorio privado

context.getCashDir().getAbsolutePath();

Mecanismo de carga de clases del cargador de clases de Android

El cargador de clases de Android también adopta el mecanismo de delegación de padres. Cuando llamamos a classloader.loadClass, primero verificamos qué cargador de clases se necesita de acuerdo con la delegación de padres
para encontrar la clase.

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

método findClass de 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;
    }

Del código anterior podemos ver que,

Cada objeto del cargador de clases tiene su propia responsabilidad de cargar la clase correspondiente.
Dentro del cargador de clases, hay un objeto dexpathlist de la clase miembro Dexpathlist. Dexthlist tiene una matriz de elementos Element[], y hay n elementos dentro. La clase Element tiene un dexFile variable, y este dexFile es Representa cada archivo dex.

Entonces classloader findclass para encontrar una clase es encontrarla en la matriz de elementos en el objeto dexpathlist

Al almacenar datos al principio, el cargador de clases llamará al método makeElements() en Dexpathlist para convertir el archivo dex en una clase Element, y varios archivos dex List dex se convertirán en una matriz de elementos Element[].

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

Entonces,
el proceso real cuando el cargador de clases findclass carga clases es:

classloader.loadclass> classloader findclass >dexpathlist finclass > recorrer el dexFilie de cada elemento en la matriz de elementos para llamar >
dexFile loadclassBinaryName()>defindclass()>defindNative(), es un método nativo, y finalmente ir a la capa nativa para Encuéntralo.

Supongo que te gusta

Origin blog.csdn.net/weixin_43836998/article/details/126288460
Recomendado
Clasificación