What is thermal repair

To put it bluntly, hot repair means “patching”. For example, when your company launches an app, users respond that there is a major bug and urgent repair is needed. As usual, programmers work overtime to fix bugs, test them, repackage them, and release them. The problem is high cost and low efficiency. And that’s where thermal repair comes in. Replace buggy code with bug-free code downloaded from the web through a pre-defined interface. This saves much trouble, user experience is good.

Two, the principle of thermal repair

1.Android class loading mechanism

Android class loaders are divided into two types,PathClassLoader and DexClassLoader, both of which inherit from BaseDexClassLoader

The PathClassLoader code is located in libcore\ Dalvik \ SRC \main\ Java \dalvik\ System \ pathClassLoader.java The DexClassLoader code is located in libcore\ Dalvik \ SRC \main\ Java \ Dalvik \system\ dexClassLoader.java The BaseDexClassLoader code is located in libcore\ Dalvik \ SRC \main\ Java \ Dalvik \system\ basedexClassloader.java

  • PathClassLoader
  • Used to load system classes and application classes

  • DexClassLoader

    Used to load JAR, APK, dex files. Loading JAR and APK is also the final extraction of Dex file inside for loading.

2. Hot repair mechanism

Take a look at the PathClassLoader code

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

DexClassLoader code

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

The two classloaders are two or three lines of code, just calling the constructor of the parent class.

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
        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

Create an instance of the DexPathList class in the BaseDexClassLoader constructor. The DexPathList constructor creates a dexElements array

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; ArrayList suppressedExceptions = new ArrayList(); // Create an array this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); . }Copy the code

BaseDexClassLoader then overrides the findClass method and calls pathList.findClass, jumping into the DexPathList class.

/* package */final class DexPathList { ... Public Class findClass(String name, List suppressed) {// Loop through this array for (Element Element: DexElements) {// Initialize DexFile DexFile dex = element.dexfile; if (dex ! Class clazz = dex.loadClassBinaryName(name, definingContext,) {// Call DexFile loadClassBinaryName to return Class instance clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz ! = null) { return clazz; } } } return null; }... }Copy the code

It iterates through the array and initializes DexFile. If DexFile is not empty, it calls the loadClassBinaryName method of the DexFile Class to return the Class instance. The ClassLoader iterates through the array and loads the dex file in the array. The ClassLoader will not load the buggy class after loading the correct class. We will place the correct class in the Dex file, which is placed before the dexElements array.

Here is a problem, please refer to qzone team’s Android App hot patch dynamic repair technology introduction To sum up, if the referenced class and the referenced class (direct reference relationship) are in the same Dex, the referenced class is marked with CLASS_ISPREVERIFIED when the VM is started, so that the referenced class cannot perform hot repair operations. Then we must prevent the referenced classes from being marked with the CLASS_ISPREVERIFIED flag. The method of qzone is to insert a piece of code in all constructors that reference the class, and the code references another class.

Examples of thermal repair

I used the address of AndFix, the open source hotfix framework of Ali

It works by dynamically loading a class file and then calling reflection to fix it. See the Java ClassLoader loading mechanism in my last article

AndFix stands for “Android hot-fix.” It supports Android versions 2.3 to 6.0 and arm and X86 devices. Perfect support for Dalvik and ART Runtime. The AndFix patch file is an.apatch file.

This is a Demo I wrote in Eclipse.

1. Extract AndFix as a library dependency

2. Create a new AndFixDemo project that relies on the AndFix library

2.1

Create a MyApplication that inherits Application

public class MyApplication extends Application { private static final String TAG = "MyApplication"; Private static final String APATCH_PATH = "/Dennis. Apatch "; private PatchManager mPatchManager; @Override public void onCreate() { super.onCreate(); mPatchManager = new PatchManager(this); MPatchManager. Init (" 1.0 "); mPatchManager.loadPatch(); String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH; File apatchPath = new File(patchFileString); If (apatchpath.exists ()) {log. I (TAG, "patch file exists "); try { mPatchManager.addPatch(patchFileString); } catch (IOException e) {log. I (TAG, "patch error "); e.printStackTrace(); }} else {log. I (TAG, "patch file does not exist "); }}}Copy the code

In fact, the apatch file must be downloaded through the network interface, which I put in the SD card root directory for demonstration purposes

2.2

In MainActivity, use a button to pop toast. Above is the buggy code, below is the corrected code

Package as bug.apk and nobug.apk, respectively

2.3

Apkpatch, a tool for generating patches, is then used

_MACOSX is for OS X. bat is for Windows

I use.bat

Put bug. apk and nobug. apk generated before, as well as the keystore file used for packaging, into the apkpatch-1.0.3 directory, open CMD, enter the apkpatch-1.0.3 directory, and enter the following command

apkpatch.bat -f NoBug.apk -t Bug.apk -o Dennis -k keystore -p 111111 -a 111111 -e 111111

The meanings of each parameter are as follows

-f The apk of the new version -t the apk of the old version -o The folder that outputs the Apatch file. You can name it any time. -k The name of the keystore file that is packaged -p keystore password -a keystore user alias -e keystore user alias password

If you add modified… The apkpatch-1.0.3 directory has been added to Dennis

I changed this file to Dennis.apatch

2.4

Bug. Apk is running on the phone

Then place Dennis.apatch in the SD card root directory, exit the app, enter again, and press the button

Finally, attach the link to open the Demo and APK and APatch files