This type of

The ClassLoader in Java can load JAR files and Class files (essentially loading Class files), which is not true in Android because both DVM and ART load dex files instead of Class files.

The Android ClassLoader type is similar to the Java ClassLoader type. There are two types of ClassLoader: system ClassLoader and custom ClassLoader. The Android system ClassLoader includes three kinds of BootClassLoader, PathClassLoader and DexClassLoader, and the Java system ClassLoader also includes three kinds. Bootstrap ClassLoader, Extensions ClassLoader, and App ClassLoader respectively.

BootClassLoader

The Android system uses BootClassLoader to preload common classes at startup. Unlike Java BootClassLoader, it is implemented by Java instead of C/C++ code. The code for BootClassLoade is shown below

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

    ...
}
Copy the code

BootClassLoader is an internal class of ClassLoader and inherits from ClassLoader. BootClassLoader is a singleton class. Note that the BootClassLoader access modifier is default and can only be accessed in the same package, so we cannot call it directly in the application.

PathClassLoader

Android uses PathClassLoader to load system classes and application classes. If non-system application classes are loaded, dex files under Data /app/$packagename and APK files or JAR files containing dex will be loaded. No matter what kind of file is loaded, the dex file must be loaded eventually. Here, we collectively refer to dex files and APK files or JAR files containing dex as dex-related files for easy understanding. It is not recommended to use PathClassLoader directly.

// 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); }}Copy the code

PathClassLoader inherits from BaseDexClassLoader. Obviously, the methods of PathClassLoader are implemented in BaseDexClassLoader.

The constructor for the PathClassLoader takes three arguments:

  • DexPath: indicates the set of paths to the dex file and apK file or JAR file containing the dex. Multiple paths are separated by file delimiters (:) by default.
  • LibrarySearchPath: a collection of paths containing C/C++ libraries. Multiple paths are separated by file delimiters, which can be null
  • Parent: indicates the parent of the ClassLoader

DexClassLoader

DexClassLoader can load dex files and APK files or JAR files containing dex, and can also load files from SD cards. This means that DexClassLoader can load files related to dex without the application being installed. As such, it is the foundation of hot repair and plug-in technologies.

public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); }}Copy the code

The DexClassLoader constructor takes one more optimizedDirectory argument than the PathClassLoader constructor. When the application is loaded for the first time, in order to improve the later startup speed and execution efficiency, the Android system will optimize the dex related files to a certain extent and generate an ODEX file. When the application is run again, it only needs to load the optimized ODEX file. The optimizedDirectory parameter represents the path to the ODEX file, which must be an internal storage path. The PathClassLoader does not have the parameter optimizedDirectory, because the PathClassLoader already defaults the parameter optimizedDirectory to /data/dalvik-cache. DexClassLoader also inherits from BaseDexClassLoader, and the method implementation is also in BaseDexClassLoader.

As for the above ClassLoader creation process in Android system, this involves Zygote process, which is not the focus of this article, so it will not be discussed here.

ClassLoader inheritance relationship

 

 

  • ClassLoaderIs an abstract class that defines theClassLoaderThe main function of.BootClassLoaderIs its inner class.
  • SecureClassLoaderClasses andJDK8In theSecureClassLoaderClass code is the same, it inherits the abstract classClassLoader.SecureClassLoaderIs notClassLoaderImplementation class, but extendedClassLoaderClass has been enhanced by adding permissionsClassLoaderSecurity.
  • URLClassLoaderClasses andJDK8In theURLClassLoaderClass code is the same, it inherits fromSecureClassLoaderTo load classes and resources from JAR files and folders through the URl path.
  • BaseDexClassLoaderInherited fromClassLoaderIs an abstract classClassLoaderThe concrete implementation class of,PathClassLoaderandDexClassLoaderInherit it.

Here’s a look at the types of classloaders you need to run an Android application

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) setContentView(r.layout.activity_main) var classLoader = this.classLoader // Print ClassLoader inheritance relationship while (ClassLoader! = null) { Log.d("MainActivity", classLoader.toString()) classLoader = classLoader.parent } } }Copy the code

The MainActivity class loader is printed and the parent of the current class loader is printed until there is no parent, then the loop is terminated. The print result is as follows:

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.hotfi xdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]] com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926Copy the code

 

You can see that there are two types of loaders, one is PathClassLoader and the other is BootClassLoader. DexPathList contains many paths, including/data/app/com zhgqthomas. Making. Hotfixdemo – 2 / base. Apk is sample application installed on a mobile phone location.

Parental delegation pattern

The parent delegation mode is used by the Class loader to find the Class. The so-called parent delegation mode is to determine whether the Class has been loaded, and if not, it does not find the Class itself but delegates it to the parent loader to find the Class, and so on, and so on, until the delegation to the top of the BootstrapClassLoader. If the BootstrapClassLoader finds the Class, the BootstrapClassLoader returns it. If it doesn’t find the Class, the BootstrapClassLoader continues to look for it. This is the implementation logic of the ClassLoader in the JDK. There are differences in the logic handling of the findBootstrapClassOrNull method in Android ClassLoader.

// 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 ! 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; }Copy the code

The code above is easy to understand. It first looks to see if the loaded class has already been loaded, and if it returns directly, otherwise the parent loader is delegated to look until there is no parent loader and the findBootstrapClassOrNull method is called.

How is findBootstrapClassOrNull implemented in the JDK and Android

// JDK ClassLoader.java private Class<? > findBootstrapClassOrNull(String name) { if (! checkName(name)) return null; return findBootstrapClass(name); }Copy the code

FindBootstrapClassOrNull in the JDK will eventually be sent to the BootstrapClassLoader to find the Class file. As mentioned above, BootstrapClassLoader is implemented by C++. So findBootstrapClass is a native method

// JDK ClassLoader.java private native Class<? > findBootstrapClass(String name);

FindBootstrapClassOrNull is implemented differently in Android than in the JDK

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

On Android, this method returns NULL because BootstrapClassLoader is not required

It is the Class loader that looks for the parent delegate pattern adopted by the Class, so reflection can be used to modify the order in which the Class loader loads the dex-related files to achieve a hot fix

Class loading process

It can be seen from the above analysis

  • PathClassLoaderYou can load the dex file in the Android system
  • DexClassLoaderCan load any directorydex/zip/apk/jarFile, but specifyoptimizedDirectory.

It can be seen from the code that these two classes only inherit BaseDexClassLoader, and the specific implementation is still completed by BaseDexClassLoader.

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); }... }Copy the code

As you can see from the BaseDexClassLoader constructor, the most important thing is to initialize the pathList, which is the DexPathList class, which is mainly used to manage the dex related files

// 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); 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; }Copy the code

The most important part of the BaseDexClassLoader is the findClass method, which loads the corresponding class file in the dex file. FindClass is ultimately handled by the DexPathList class

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); . }}Copy the code

Looking at the code of the DexPathList core constructor, you can see that the DexPathList class stores dex paths through Element and loads dex files and returns the Element collection through the makeDexElements function

// 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)) {if (name.endswith (DEX_SUFFIX)) {if (name.endswith (DEX_SUFFIX)) {if (name.endswith (DEX_SUFFIX)) {if (name.endswith (DEX_SUFFIX)) { 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 {// If apk, jar, zip etc. 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); If (dex == null) {elements[elementsPos++] = new Element(file); // wrap the dex file or zip file as an Element object and add it to the Element collection. } 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; }Copy the code

In general, the constructor of DexPathList is to wrap the dex related files (perhaps dex, APK, JAR, zip, which are defined at the beginning) into an Element object that is eventually added to the Element collection

In fact, Android class loaders, either PathClassLoader or DexClassLoader, only recognize dex files in the end, and loadDexFile is the core method of loading dex files. Dex can be extracted from jar, APK, and ZIP

// 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; }Copy the code

DexElements is already initialized in the DexPathList constructor, so this method is easy to understand. It simply iterates through the Element array and returns the class as soon as it finds a class with the same name, or null if it doesn’t

Thermal repair implementation

From the above analysis, we can know that running an Android program uses the PathClassLoader, That is, BaseDexClassLoader, and dex related files in APK are stored in the dexElements attribute of the pathList object of BaseDexClassLoader.

So the idea behind hot fixes is to put the bugged dex files in the head of the dexElements collection. This traverse will first traverse the dex and find the fixed class. Because of the classloader’s parent delegate mode, Classes with bugs in old dex have no chance to play. This allows you to fix existing bug classes without releasing a new version

Manually implement the hot repair function

According to the above principle of hot repair, the corresponding ideas can be summarized as follows

  1. createBaseDexClassLoaderA subclass ofDexClassLoaderloader
  2. Load the fixed class.dex (fix pack downloaded by the server)
  3. Will be self-contained and systematicdexElementsMake merge and set freedexElementspriority
  4. By reflection technique, assigned to the systempathList