[Fat Tiger's Reverse Road] 01 - Explicación detallada de la carga dinámica y el mecanismo de carga de clase

I. Introducción

He sido consciente de empacar y desempaquetar antes, usando Fart y otras herramientas de desempaquetado directamente, permaneciendo en el nivel de saber lo que no sé por qué, así que para prepararnos para el estudio de la teoría básica de Android, primero debemos tener un comprensión profunda de los cargadores de clases y la relación entre la carga dinámica y la carga dinámica. Este artículo registra la relación y el principio entre el cargador de clases y la carga dinámica. Debido a la capacidad limitada del autor, haré todo lo posible para explicar la relación entre los dos. en detalle Si hay algún error en este artículo, corríjame, gracias ~


En segundo lugar, el cargador de clases

El mecanismo del cargador de clases en Android sigue el modelo de delegación parental (carga parental) como la JVM

Tanto la delegación de padres como la carga de padres se refieren al mismo mecanismo, solo que lo llamaron de manera diferente...

1. Modelo de delegación de padres

Primero, echemos un vistazo a la explicación de la industria sobre el mecanismo de delegación de los padres.

1) Al cargar un archivo .class, se le confía recursivamente al cargador principal ParentClassLoader para que lo cargue. Si se ha cargado, no es necesario volver a cargarlo. 2) Si el cargador principal no se ha cargado, continúe
delegando al cargador principal Para cargar, hasta la parte superior de este enlace, si el ClassLoader superior no se ha cargado, intente cargar, si la carga falla, se devolverá a la persona que llama para que lo cargue paso a paso

Es muy conciso y claro, pero puede ser difícil para los estudiantes que no entienden. Aquí probablemente hice un dibujo (el estilo de la pintura está distorsionado, observen con atención...)

inserte la descripción de la imagen aquí

De la imagen de arriba combinada con la descripción del texto, debería ser casi comprensible, ¿verdad?
Así que déjame decirlo de nuevo:

1) Primero verifique si el ClassLoader actual ha cargado el archivo de clase, use el método findLoadedClass, si se ha cargado, regrese directamente
2) Si el ClassLoader actual no se ha cargado, y hay una clase principal (juzgando que no es el ClassLoader de nivel superior), luego delegue Para cargar la clase principal, use el método parent.loadClass(name, false). En este momento, se pasará hacia arriba y luego vaya al ClassLoader principal para completar el ciclo del paso 1 hasta que arriba ClassLoader (3) Si el ClassLoader principal no está cargado, pruebe este
nivel de carga de ClassLoader, si la carga falla, se transmitirá y se entregará al método de llamada para realizar la carga del archivo .class

En este punto, probablemente puedas entender, ¿verdad?

Bueno, si todavía no lo entiende aquí, parece que tengo expectativas demasiado altas para mi escritura. El siguiente es un pasaje que copié (de Baidu Google)

Queremos cargar un archivo de clase, y definimos un cargador de clases CustomerClassLoader:
(1) Primero, juzgará si su CustomerClassLoader ha sido cargado, y si lo ha sido, regresará directamente,
(2) Si no lo ha sido sido cargado, llamará a la clase padre PathClassLoader para cargar, la clase padre también juzgará si lo ha cargado. Si no lo ha cargado, lo confiará a la clase padre BootClassLoader para que lo cargue. (3) Este BootClassLoader
es el classLoader superior. También juzgará si lo ha cargado. Si no lo ha cargado, entonces llamará a su propio findClass (nombre) para cargar, (4)
Si el BootClassLoader de nivel superior no se carga, devolverá la acción de carga a PathClassLoader,
(5) este PathClassLoader también intentará llamar a findClass(name); para cargar, si la carga falla, continuará regresando a CustomClassLoader para completar la carga. Todo este proceso se siente como un proceso recursivo, subiendo gradualmente y luego bajando gradualmente hasta que la carga sea exitosa.De hecho, este String.class está en el sistema cuando se inicia.Ha
sido cargado, definimos un CustomerClassLoader para cargar, de hecho, también lo carga el clase padre

Entonces, ¿por qué existe tal cosa para limitar la carga de archivos de clase?

(1) Evitar que el mismo archivo .class se cargue repetidamente
(2) Garantizar la unicidad de cualquier clase en la máquina virtual El cargador de clases que lo carga y el nombre completo de la clase establecen su unicidad en la máquina virtual Java Unicidad
(3) Asegúrese de que el archivo .class no se altere y que se pueda garantizar que la lógica de carga de la clase del sistema no se altere a través de la delegación.

Aquí viene el punto de conocimiento, ¿cómo asegurar la unicidad de una clase?

No solo el nombre completo de la clase, sino también el cargador de clases que carga la clase y el nombre completo de la clase juntos determinan la singularidad en el jvm


2. Mecanismo de carga de clases en Android

1) Precarga de clases básicas de Android

Primer vistazo al inicio de la máquina virtual Dalvik (la imagen está copiada)
inserte la descripción de la imagen aquí

En lenguaje sencillo, dice así:

  1. Inicio del cargador de arranque (inicio del botón de encendido) para
  2. Después de que el kernel inicia el proceso inactivo
  3. Después de que la capa Nativate ejecute el proceso Init
  4. El análisis ejecuta init.rc, y dentro de él
  5. Llamado app_process (la base de Xposed es reemplazar app_process), y luego ingrese
  6. La capa de marco genera el proceso de cigoto. Cuando se inician otros procesos de la aplicación, la incubación del proceso se lleva a cabo en cigoto. Finalmente, nuestro
  7. Cada proceso system_server comienza y termina

Por supuesto, hemos simplificado muchos procesos, como el trabajo principal del proceso nativo del cigoto. No entraré en detalles sobre esto por ahora, y abriré un artículo separado para hablar sobre el cigoto en el futuro~


De vuelta a nuestro cargador de clases...

2) Análisis y relación jerárquica del cargador de clases de Android

La siguiente figura es un diagrama esquemático de la relación jerárquica (no lo adivine, soy de Baidu), que muestra claramente la relación y la jerarquía entre los cargadores, consulte ~

inserte la descripción de la imagen aquí

Los tipos de ClassLoader en Android se dividen en ClassLoader de sistema y ClassLoader personalizado.
Entre ellos, el sistema ClassLoader incluye tres tipos: BootClassLoader, DexClassLoader y PathClassLoader
(1) BootClassLoader: todos los sistemas Android en la plataforma Android usarán BootClassLoader para precargar las clases de uso común cuando se inician
(2) BaseDexClassLoader: la carga de la aplicación real archivos de clase de capa, y la carga real se confía a pathList para completar
(3) DexClassLoader: puede cargar archivos dex y archivos comprimidos que contienen dex (apk, dex, jar, zip), y puede instalar un archivo apk desinstalado, generalmente una clase personalizada loader (4
) PathClassLoader: la clase que puede cargar clases y aplicaciones del sistema, generalmente se usa para cargar el archivo dex del apk instalado.
Suplemento:
el cargador nativo proporcionado por Android se denomina cargador de clases básico, que incluye: BootClassLoader, PathClassLoader, DexClassLoader, InMemoryDexClassLoader (introducido en Android 8.0), DelegateLastClassLoader (introducido en Android 8.1)

emm Si tienes un poco de gusto, creo que estará bien en este punto. Los siguientes serán los detalles específicos. ¿Estás listo?


3)Cargador de clases de arranque

El cargador de la clase de inicio se usa para registrar las clases básicas que ha precargado el proceso de zygote. Se puede especular que solo necesita cargarse desde el caché. Esta es la clase base ClassLoader y finalmente una clase interna. Debido a la derechos de acceso del paquete, la capa de aplicación no tiene forma de entrevista directa

Echemos un vistazo a su código fuente.

public abstract class ClassLoader {
    
    
    // ...省略
 
    class BootClassLoader extends ClassLoader {
    
    
        private static BootClassLoader instance;
        public static synchronized BootClassLoader getInstance() {
    
    
            if (instance == null) {
    
    
                instance = new BootClassLoader();
            }
            return instance;
        }
        public BootClassLoader() {
    
    
            super(null);
        }
 
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
            return Class.classForName(name, false, null);
        }
 
        // ...省略
        @Override
        protected Class<?> loadClass(String className, boolean resolve)
               throws ClassNotFoundException {
    
    
            Class<?> clazz = findLoadedClass(className);
            if (clazz == null) {
    
    
                clazz = findClass(className);
            }
            return clazz;
        }
 
        // ...省略
    }
}

En el análisis del código fuente, podemos ver que BootClassLoader no tiene un cargador de clases principal. Cuando la clase no se puede recuperar del caché, llama directamente a su propio método findclass. El método findClass() llama al método Class.classForName y ZygoteInit. preloadClasses() , cargando la clase base es Class.forName()

ublic final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    
    
    // ...省略
 
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
    
    
        Class<?> caller = Reflection.getCallerClass();
        return forName(className, true, ClassLoader.getClassLoader(caller));
    }
 
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
    
    
        if (loader == null) {
    
    
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
    
    
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
    
    
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
    
    
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }
 
    // 本地方法
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;
 
    // ...省略
}

Mirando el código fuente, podemos encontrar que cuando se precarga, ZygoteInit.preloadClasses() llama a Class.forName(), que en realidad especifica BootClassLoader como el cargador de clases e inicializa el proceso BootClassLoader solo una vez. En resumen, a través de Class.forName( ) o
Class .classForName() puede y solo puede cargar la clase básica directamente. Una vez precargada la clase básica, aunque no podemos acceder directamente a BootClassLoader para la aplicación, se puede cargar a través de Class.forName/Class.classForName, lo que significa que se puede cargar a través de Class
.forName obtiene una instancia de la clase básica?

Mira el proceso aproximado

inserte la descripción de la imagen aquí

Desde el inicio de Zygote, hasta la creación de vm, la inicialización y la acumulación de archivos dex, zygoteinit precarga las clases básicas e incuba cada proceso Appp de aplicación para cargar clases básicas y algunas clases relacionadas, ya sea el cargador de clases del sistema (PathClassLoader) o la clase personalizada que carga DexClassLoader
, el cargador antecesor más alto tiene como valor predeterminado BootClassLoader, que, al igual que JVM, garantiza la seguridad de tipo de las clases básicas

La carga de la clase básicamente ha llegado a su fin, descansemos cinco minutos... Veamos la carga del archivo Class

Subproceso{dormir(5_000)}...


4) Carga de archivos de clase

1. Carga dinámica mediante el método Class.forName()
2. Carga dinámica mediante el método ClassLoader.loadClass()
La carga de clases se divide en 3 pasos: 1. Cargar (Load), 2. Vincular (Link), 3. Inicializar ( inicializar)

inserte la descripción de la imagen aquí
Momento de carga de la clase:

1. Carga implícita:
(1) Crear una instancia de una clase, que es un objeto nuevo
(2) Acceder a una variable estática de una determinada clase o interfaz, o asignar un valor a la variable estática
(3) Llamar a un método estático de la clase
(4) Reflection Class.forName("android.app.ActivityThread")
(5) Inicializar una subclase de una clase (la clase principal de la subclase se inicializará primero)
2. Carga explícita:
(1) Use LoadClass( ) para cargar
(2) Use forName() para cargar

Hay dos métodos en la carga explícita y los dos métodos son ligeramente diferentes.

(1) El método ClassLoader.loadclasses puede cargar una clase, pero no activará la inicialización de la clase, es decir, no inicializará las variables estáticas y los bloques de código en la clase (2) El método Class.forName no solo cargará una clase
, sino que también activará la fase de inicialización de la clase para inicializar las variables estáticas y los bloques de código de esta clase (se ejecutará el bloque de código)


5)PathClassLoader

PathClassLoader se usa principalmente para cargadores de clases de aplicaciones y sistemas, donde el directorio optimizado es nulo y se usa el directorio predeterminado /data/dalvik-cache/

PathClassLoader se utiliza como cargador de clases del sistema de la aplicación y también se inicializa cuando se inicia el proceso Zygote (el proceso básico es: ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit .createPathClassLoader( ).Se ejecuta después de precargar la clase básica), por lo que cada proceso de la aplicación lleva automáticamente un PathClassLoader después de bifurcarse de Zygote, que generalmente se usa para cargar el archivo .dex en el apk

Después de que cada proceso de la aplicación nace de zygote, lleva automáticamente un pathClassLoader, que generalmente se usa para cargar el archivo .dex en el apk.

6)Cargador de clases Dex

El cargador de clases que puede cargar clases desde el jar o apk que contiene classs.dex se puede usar para realizar una carga dinámica, pero debe ser un directorio de escritura privado de la aplicación para almacenar en caché los archivos odex. Puede cargar archivos apk o jar que no están instalados en el sistema, por lo que muchas correcciones urgentes y soluciones complementarias utilizan DexClassLoader

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

Resumen:
al observar el código fuente, podemos encontrar que tanto DexClassLoader como PathClassLoader heredan de BaseDexClassLoader. Estas dos clases solo proporcionan sus propios constructores sin implementación adicional. Diferencia: DexClassLoader proporciona un directorio optimizado, mientras que PathClassLoader no lo hace. OptimizedDirectory se usa para almacenar
odex
El lugar del archivo, por lo que puede usar DexClassLoader para lograr una carga dinámica


7) El proceso de carga de BaseDexClassLoader

Sáltelo por ahora (todavía no lo entiendo bien)


8) LoadClass() de ClassLoader

public abstract class ClassLoader {
    
    
 
    public Class<?> loadClass(String className) throws ClassNotFoundException {
    
    
        return loadClass(className, false);
    }
 
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    
    
        //判断当前类加载器是否已经加载过指定类,若已加载则直接返回
        Class<?> clazz = findLoadedClass(className);
 
        if (clazz == null) {
    
    
            //如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
            clazz = parent.loadClass(className, false);
 
            if (clazz == null) {
    
    
                //还没加载,则调用当前类加载器来加载
                clazz = findClass(className);
            }
        }
        return clazz;
    }
}

El proceso de carga de este método es el siguiente:
(1) Determine si el cargador de clases actual ha cargado la clase especificada, si se ha cargado, regrese directamente; de ​​lo contrario, continúe con la ejecución; (2)
Llame a la carga de clases principal para cargar recursivamente el class, verifique si está cargado, si se ha cargado, regresará directamente, de lo contrario continuará ejecutándose; (
3) Llame al cargador de clases actual y cárguelo a través de findClass.

9) Carga de findLoadedClass() de ClassLoader

protected final Class<?> findLoadedClass(String name) {
    
    
    ClassLoader loader;
    if (this == BootClassLoader.getInstance())
        loader = null;
    else
        loader = this;
    return VMClassLoader.findLoadedClass(loader, name);
}

En resumen, como se muestra en la siguiente figura

inserte la descripción de la imagen aquí

3. Resumen del artículo

Después de trabajar durante mucho tiempo, todavía no lo he descubierto del todo. Las células cerebrales de hoy están aquí. Feliz año nuevo a todos.

4. Referencias

https://bbs.kanxue.com/thread-271538.htm

Supongo que te gusta

Origin blog.csdn.net/a_Chaon/article/details/128577763
Recomendado
Clasificación