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
, PathClassLoader
y DexClassLoader
, y el cargador de clases del sistema Java también incluye tres tipos, a saber Bootstrap ClassLoader
, Extensions 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 ClassLoader
. BootClassLoader
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/$packagename
cargará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);
}
}
PathClassLoader
Heredado 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
ClassLoader
Es una clase abstracta en la queClassLoader
se definen las funciones principales.BootClassLoader
Es su clase interna.SecureClassLoader
El código de la clase yJDK8
laSecureClassLoader
clase en es el mismo, hereda la clase abstractaClassLoader
.SecureClassLoader
No esClassLoader
la realización de la clase, sino laClassLoader
clase expandida para sumarse a la función de permisos y fortalecer laClassLoader
seguridad.URLClassLoader
El código de la clase yJDK8
laURLClassLoader
clase en es el mismo, hereda de élSecureClassLoader
y se usa para cargar clases y recursos de archivos jar y carpetas a través de la ruta URL.BaseDexClassLoader
Se hereda deClassLoader
, esClassLoader
la clase de implementación concreta de la clase abstracta ,PathClassLoader
yDexClassLoader
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 PathClassLoader
y 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
ClassLoader
ClassLoader
findBootstrapClassOrNull
// 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 Android
en 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 AndroidDexClassLoader
Puede cargardex/zip/apk/jar
archivos en cualquier directorio , pero debe especificarlooptimizedDirectory
.
Estas dos clases conocidas por el código recién heredado BaseDexClassLoader
, la implementación específica aún está BaseDexClassLoader
completa.
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 loadDexFile
es 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 pathList
objeto 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
- Crear un cargador de
BaseDexClassLoader
subclaseDexClassLoader
- Cargue el class.dex reparado (el paquete de reparación descargado por el servidor)
dexElements
Combine propio y sistema , y establezca ladexElements
prioridad de la libertad- Mediante tecnología de reflexión, asignada al sistema
pathList