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