¡Un artículo para comprender el principio del esquema de carga de clases de reparación en caliente!

Tipo de cargador de clases

En Java,  ClassLoader puede cargar archivos jar y archivos de clase (básicamente, cargar archivos de clase). Esto no es aplicable en Android, porque tanto DVM como ART ya no son archivos de clase, sino archivos dex.

Los ClassLoader tipos de Android son  ClassLoader similares a los de Java y  también se dividen en dos tipos, a saber 系统 ClassLoader y 自定义 ClassLoader. Entre ellos, Android  系统 ClassLoader incluye tres tipos, a saber  BootClassLoader, PathClassLoaderDexClassLoader, y el cargador de clases del sistema Java también incluye tres tipos, a saber  Bootstrap ClassLoaderExtensions ClassLoader y  App ClassLoader.

BootClassLoader

Cuando se inicia el sistema Android, se utilizará  BootClassLoader para precargar clases comunes. A diferencia de Java  BootClassLoader , no se implementa mediante código C / C ++, sino mediante Java. BootClassLoade El código es el siguiente

// libcore/ojluni/src/main/java/java/lang/ClassLoader.java
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();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }

    ...
}

BootClassLoader Sí  ClassLoader clase interna, y heredada  ClassLoaderBootClassLoader Es una clase singleton, cabe señalar que  **BootClassLoader** el modificador de acceso es predeterminado y solo se puede acceder en el mismo paquete, por lo que no podemos llamarlo directamente en la aplicación .

PathClassLoader

El sistema Android se usa  PathClassLoader para cargar clases de sistema y clases de aplicación. Si va a cargar clases de aplicación que no son del sistema, se  data/app/$packagenamecargarán el archivo dex y el archivo apk o el archivo jar que contiene dex. No importa qué archivo se cargue, eventualmente se cargará Cargue el archivo dex. Para facilitar la comprensión, nos referiremos colectivamente al archivo dex y al archivo apk o archivo jar que contiene dex como el archivo relacionado dex. PathClassLoader no se recomienda para desarrollo y uso directo.

// libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

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

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

PathClassLoaderHeredado de  BaseDexClassLoader, la PathClassLoader implementación del método obvio  está en  BaseDexClassLoader .

PathClassLoader El método de construcción tiene tres parámetros:

  • dexPath: la colección de rutas de archivos dex y archivos apk o archivos jar que contienen dex. Varias rutas están separadas por separadores de archivos. El separador de archivos predeterminado es ':'.
  • librarySearchPath: una colección de rutas que contienen bibliotecas C / C ++, varias rutas están separadas por separadores de archivos, que pueden ser nulos
  • padre : ClassLoader 的 padre

DexClassLoader

DexClassLoader Puede cargar archivos dex y archivos apk o archivos jar que contengan dex, y también admitir la carga desde una tarjeta SD, lo que significa que DexClassLoader los archivos relacionados con dex se pueden cargar  cuando la aplicación no está instalada. Por lo tanto, es la base de la tecnología de reparación en caliente y plug-in.

public class DexClassLoader extends BaseDexClassLoader {

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

DexClassLoader El parámetro del método de construcción es más  PathClassLoader de un  optimizedDirectory parámetro optimizedDirectory ¿Qué representa el parámetro  ? Cuando la aplicación se carga por primera vez, para mejorar la velocidad de inicio y la eficiencia de ejecución en el futuro, el sistema Android optimizará los archivos relacionados con dex hasta cierto punto y generará un  ODEX archivo. Luego, cuando la aplicación se ejecute nuevamente, simplemente cárguela El ODEX archivo optimizado  es suficiente, ahorrando tiempo para optimizar cada vez, y el parámetro  optimizedDirectory es ODEX la ruta del archivo de almacenamiento  , y esta ruta debe ser una ruta de almacenamiento interno. PathClassLoader Ningún argumento  optimizedDirectory, porque  PathClassLoader ya el parámetro por defecto  optimizedDirectory ruta es: /data/dalvik-cache. DexClassLoader También heredado de  BaseDexClassLoader , la implementación del método también está  BaseDexClassLoader en.

Con respecto al ClassLoader proceso de creación anterior  en el sistema Android, el proceso está involucrado aquí  Zygote , que no es el enfoque de este artículo, por lo que no lo discutiré aquí.

Relación de herencia de ClassLoader

image.png

  • ClassLoader Es una clase abstracta en la que ClassLoader se definen las  funciones principales. BootClassLoader Es su clase interna.
  • SecureClassLoader El código de la clase y  JDK8 la  SecureClassLoaderclase en es el mismo, hereda la clase abstracta  ClassLoader. SecureClassLoader No es  ClassLoader la realización de la clase, sino la ClassLoader clase expandida  para sumarse a la función de permisos y fortalecer la  ClassLoader seguridad.
  • URLClassLoader El código de la  clase y  JDK8 la  URLClassLoaderclase en es el mismo, hereda de él  SecureClassLoadery se usa para cargar clases y recursos de archivos jar y carpetas a través de la ruta URL.
  • BaseDexClassLoader Se hereda de  ClassLoader, es ClassLoader la clase de implementación concreta de la clase abstracta  , PathClassLoaderDexClassLoader ambas la heredan.

Echemos un vistazo a los distintos tipos de cargadores de clases necesarios para ejecutar un programa de Android

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var classLoader = this.classLoader

        // 打印 ClassLoader 继承关系
        while (classLoader != null) {
            Log.d("MainActivity", classLoader.toString())
            classLoader = classLoader.parent
        }
    }
}

Se  MainActivity imprime el cargador de clases y se imprime el cargador del cargador de clases padre actual, el cargador hasta que no hay padre, entonces el ciclo termina. El resultado de la impresión es el siguiente:

com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]]

com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926

Puede ver que hay dos tipos de cargadores de clases, uno es  PathClassLoadery el otro es  BootClassLoader. DexPathList Hay muchas rutas en él, incluida /data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk la ubicación donde  está instalada la aplicación de muestra en el teléfono.

Modelo de delegación principal

El cargador de clases usa el modo de delegación parental para encontrar la clase. El modo de delegación parental es para determinar primero si la clase se ha cargado. De lo contrario, no se busca por sí mismo, sino que se confía al cargador principal para la búsqueda, y luego de forma recursiva hasta Encargado al nivel superior BootstrapClassLoader, si se  encuentra la Clase, volverá directamente, si no se encuentra, seguirá mirando hacia abajo una a una, y si no se encuentra, finalmente se entregará a sí mismo para encontrarla.  Esta es  la lógica de implementación en JDK   y existen diferencias  en  el procesamiento lógico de los métodos en Android  .BootstrapClassLoader ClassLoaderClassLoaderfindBootstrapClassOrNull

// ClassLoader.java

    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) {
                long t0 = System.nanoTime();
                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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

El código anterior es fácil de entender. Primero, descubrirá si la clase cargada se ha cargado. Si se devuelve directamente, de lo contrario, se delegará al cargador principal para buscar y se llamará al findBootstrapClassOrNull método hasta que no haya ningún cargador principal  .

Echemos un vistazo a   cómo se implementa findBootstrapClassOrNull en  JDK y  Androiden respectivamente

// JDK ClassLoader.java

    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

JDK La  findBootstrapClassOrNull reunión finalmente se entregará  BootstrapClassLoader para encontrar el  Class archivo. Como se mencionó anteriormente  BootstrapClassLoader , findBootstrapClass está implementado por C ++, por lo que  es un método nativo

// JDK ClassLoader.java Private Native Class <?> FindBootstrapClass (Nombre de cadena);

findBootstrapClassOrNull La implementación  en Android  JDK es diferente

// Android 
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }

Android Como no es necesario utilizarlo,  BootstrapClassLoader este método devuelve directamente null

Es el uso del cargador de clases para encontrar el modo de delegación padre adoptado por la Clase, por lo que el orden en el que el cargador de clases carga los archivos relacionados con dex puede modificarse por reflexión para lograr el propósito de la reparación en caliente.

Proceso de carga de clases

A través del análisis anterior, podemos ver

  • PathClassLoader Puede cargar archivos dex en el sistema Android
  • DexClassLoader Puede cargar dex/zip/apk/jar archivos en cualquier directorio  , pero debe especificarlo optimizedDirectory.

Estas dos clases conocidas por el código recién heredado  BaseDexClassLoader, la implementación específica aún está  BaseDexClassLoadercompleta.

BaseDexClassLoader

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

public class BaseDexClassLoader extends ClassLoader {

    ...

    private final DexPathList pathList;

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

    /**
     * @hide
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }

    ...

    public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

    ...
}

Por  BaseDexClassLoader saber constructor para inicializar lo más importante es  pathList que es  DexPathList esta clase, que se usa principalmente para administrar documentos dex

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions); // 查找逻辑交给 DexPathList
        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;
    }

BaseDexClassLoader Lo más importante es este  findClass método, que se utiliza para cargar el archivo correspondiente en el class archivo dex  . Y al final se entrega a la  DexPathList clase para que se encargue de la implementación findClass

DexPathList

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

final class DexPathList {
    ...

    /** class definition context */
    private final ClassLoader definingContext;

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;

    ...

    DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
       ...

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        ...
    }

}

Al  DexPathList observar el código del constructor principal, podemos ver que la DexPathList clase  Element se almacena a  dex 路径 través de la makeDexElements función , y el archivo relacionado con dex se carga a través de la  función, y Element se devuelve la  colección.

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      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) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) { // 判断是否是 dex 文件
                  // Raw dex file (not inside a zip/jar).
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else { // 如果是 apk, jar, zip 等文件
                  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);
                  }

                    // 将 dex 文件或压缩文件包装成 Element 对象,并添加到 Element 集合中
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
              if (dex != null && isTrusted) {
                dex.setTrusted();
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

En general, DexPathList el constructor encapsula los archivos relacionados con dex (pueden ser dex, apk, jar, zip, estos tipos se definen al principio) en un  Element objeto y finalmente agregarlo a la  Element colección.

De hecho, el cargador de clases de Android, ya sea PathClassLoader o DexClassLoader, reconocen solo los últimos archivos dex, y  loadDexFilees el método principal para cargar archivos dex que se pueden extraer de dex jar, apk, zip in

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

Se DexPathList ha inicializado en  el constructor  dexElements, por lo que este método es fácil de entender. Simplemente atraviesa la matriz de elementos. Una vez que se encuentra una clase con el mismo nombre de clase y nombre, devolverá directamente esta clase, y si no se puede encontrar, devolverá nulo.

Implementación de hotfix

A través del análisis anterior, podemos saber que la ejecución de un  Android programa se utiliza  PathClassLoader, es decir  BaseDexClassLoader, los archivos relacionados con DEX en el apk se almacenarán en   las  propiedades de BaseDexClassLoader la  pathListobjeto  dexElements.

Entonces, el principio de la corrección rápida es poner los archivos relacionados con dex del error corregido en  dexElements el encabezado de la colección, de modo que al atravesar, primero atravesará el dex reparado y encontrará la clase reparada, debido al modo de delegación padre del cargador de clases, en el antiguo dex Las clases con errores no tienen posibilidad de jugar. De esta manera, es posible corregir la clase de error existente sin lanzar una nueva versión.

Implementar manualmente la función de reparación en caliente

De acuerdo con el principio de reparación en caliente anterior, las ideas correspondientes se pueden resumir de la siguiente manera

  1. Crear un   cargador de BaseDexClassLoader subclase DexClassLoader
  2. Cargue el class.dex reparado (el paquete de reparación descargado por el servidor)
  3. dexElements Combine propio y sistema  , y establezca la dexElements prioridad de la libertad 
  4. Mediante tecnología de reflexión, asignada al sistema pathList

Supongo que te gusta

Origin blog.csdn.net/Java_Yhua/article/details/110223104
Recomendado
Clasificación