Hot repair

At present, domestic Android hot repair technology has developed can be said to bloom, from the implementation of the general classification, can be divided into: ① Native layer implementation ② Java layer implementation

Before there is a simple analysis of ali open source Andfix implementation principle (based on Native layer), see Android_hotfix _Andfix principle analysis, here is more said, interested can see This article briefly analyzes the Java layer implementation of hot repair logic, simple implementation of code hot repair Demo, Tinker as an example (of course, Tinker is to support code repair, resource repair,so repair, interested friends to move to the official website ~)

First, let’s comb through the ideas:

Java class compilation process

Dx.bat is used to compile a Java class into a. Class file, and then into a. Dex file

This introduction

The Java.lang. ClassLoader class in Android is also different from the java.lang.ClassLoader class in Java. Android ClassLoader types can also be divided into system ClassLoader and custom ClassLoader. The system ClassLoader includes three types:

  • BootClassLoaderThe Android system uses BootClassLoader to preload common classes at startup. Different from The Bootstrap ClassLoader in Java, it is implemented by Java instead of C/C++ code. BootClassLoader is an inner class of ClassLoader.
  • PathClassLoader, full name is dalvik/system. PathClassLoader that can load has been installed Apk, namely/data/app/package under the Apk, NativeLibrary can also be loaded under /vendor/lib, /system/lib.
  • DexClassLoader, full name is dalvik/system. DexClassLoader, can load a did not install the apk. PathClassLoader and DexClasLoader are inherited from dalviksystem. BaseDexClassLoader, they are all written in BaseDexClassLoader class loading logic. SecureClassLoader and UrlClassLoader are classloaders in Java and cannot be used in Android.

Dex Files are loaded

According to the source code, the.dex file is loaded through the BaseDexClassLoader class (a ClassLoader subclass), which has a member variable DexPathList object, and this object has an array of DexElement objects, that is, loaded from the file Dex file, the starting point is here, for the project, the general project will subcontract (method number is greater than 64K, and greater than 65535, the subcontracting strategy provided by Google), if the use of Java code to achieve hot repair, subcontracting is definitely done, because to ensure that the main package without bugs, subcontracting is simply packaged APK general There are multiple. Dex files, such as class. dex,classes2.dex, etc… For example, if a method of one of our classes2.dex classes fails, we can create a fix package (the fixed classes2.dex file), and then copy the fixed classes2.dex file to a private directory through our custom classloader, and then jump the queue to the system ClassL In the dexElement array of the dexPathList object of oader, let the system load the restored classes2.dex file first, so as to achieve the purpose of hot repair. This implementation method must execute the repair logic, and restart the APP to achieve the effect ~ We need to load and parse the repaired. Dex file, and then jump the queue of the old installation package. Dex file, to do the queue-jumping operation, equivalent to cheating the Android system, roughly as follows:

Realize the principle of

We need a bug-fixed.dex file. We need to queue up in the DexElement array of the DexPathList object under the BaseDexClassLoader class and sort it to the front. This article will not do a detailed introduction to the class loading mechanism, and will write a separate summary of the following steps:

The Demo implementation

#####① Basic configuration – main package configuration configure subcontracting, the main purpose of subcontracting is to package the main package will have multiple. Dex file, the actual project application to ensure that the main package does not have bugs,demo loading. Dex file also excluded the main package file classes.dex, generally as follows: Create BaseApplication, BaseActivity, MainActivity placed in the main package, including MainActivity mainly to subcontract placeholder, only click jump SecondActivity logic in the subcontract Build. Gradle in app directory, add configuration in Android →defaultConfig, where multidex-config. TXT is the class file whose configuration is kept in the main package

// Enable subcontracting multiDexEnabledtrue// Subcontract configuration, place the configuration file in the main package multiDexKeepFile file("multiDex-config.txt")
Copy the code

Add subcontracting dependencies:

//multidex subcontract dependency implementation'com. Android. Support: multidex: 1.0.3'
Copy the code

Application start subcontracting:

public class BaseApplication extends MultiDexApplication {
    @Override
    public void onCreate() { super.onCreate(); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Install multidex.install (this); }}Copy the code

② Subcontracting configuration

Subcontracting creates a SecondActivity class that does the mock exception and fix exception entry, and a Calculate mock exception that does 10/0 and fixes 10/1 Note: the fixed classes2.dex file can be obtained by directly decompressing the buildapk file, or by executing the dx.bat command under build-tools

Click the fix button on SecondActivity. See the complete code at the end of the post.

  private void update() {// Copy the downloaded fix package to a private directory and extract the corresponding. Class File from the. Dex File // Get fix package File from the SD cardsourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME); // File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath() + File.separator + Constants.DEX_NAME);if (targetFile.exists()) {
            targetFile.delete();
            Log.e("update"."Delete original DEX file (used)"); } // Copy the fix package from the SD card to the private directory fileutils.copyfile (sourceFile,targetFile);
        Log.e("update"."Copy complete");
        FixDexUtils.loadDexFile(this);
    }
Copy the code

(3) FixModule

Create a new Module to handle the hotfix logic

There are only five files, the core file code is in FixDexUtils, the others are utility classes, and one defines several constants to take a look at the code in FixDexUtils

Public class FixDexUtils {private static HashSet<File> loadedDex = new HashSet<>(); Static {loadeddex.clear (); static {loadeddex.clear (); } public static void loadDexFile(Context Context) {// File fileDir = context.getDir(Constants.DEX_DIR, Constants. Context.MODE_PRIVATE); Dex File[] listFiles = filedir.listfiles ();for(int i = 0; i < listFiles.length; I++) {// the file name ends with. Dex and is not the main package. Dex fileif (listFiles[i].getName().endsWith(Constants.DEX_SUFFIX) && !"classes.dex".equalsIgnoreCase(listFiles[i].getName())) { loadedDex.add(listFiles[i]); } // Create a custom class loader createDexClassLoader(context,fileDir); } /** * @param context * @param fileDir * create your own class loader and load the.dex file from the private directory, which has copied the dex file from the fix package to the private directory */ private static void createDexClassLoader(Context context, String optimizedDir = Filedir.getabsolutePath ()+ file.separator +"opt_dex";
        File fileOpt = new File(optimizedDir);
        if(! fileOpt.exists()) { fileOpt.mkdirs(); }for(File dex : LoadedDex) {// create your own classLoader, temporary DexClassLoader classLoader = new DexClassLoader(dex.getabsolutepath (), optimizedDir, null, context.getClassLoader()); // There is a fix file, just insert hotFix(classLoader,context); } } private static void hotFix(DexClassLoader classLoader, Context Context) {try {// Get the system's PathClassLoader (PathClassLoader)context.getClassLoader(); / / to get his own dexElements array Object myElements = ReflectUtils. GetDexElements (ReflectUtils. GetPathList (this)); / / acquisition system dexElements array Object systemElements. = ReflectUtils getDexElements (ReflectUtils. GetPathList pathClassLoader ()); / / merge array, and sorted, generate a new array Object dexElements=ArrayUtils.com bineArray (myElements systemElements); Object systemPathList = reflectutils. getPathList(pathClassLoader); // Obtain the system's pathList attribute through reflection. / / by reflection, combined new dexElements assigned to system pathList ReflectUtils. SetFieldValue (systemPathList,"dexElements",dexElements); }catch (Exception e){ e.printStackTrace(); }}}Copy the code

The main work is as follows: it is the above flow chart, which is to obtain the set of. Dex files that need to be hot repaired through traversal, decompression and other operations, traverse the set, create a temporary DexClassLoader each time, and then perform repair steps, which is divided into six steps:

The final result is shown in the figure (huawei 8.0 mobile phone is used in the Demo): Note: In order to make the effect picture more intuitive, the app has been restarted once. Note: Hot fixes implemented in this way can only be implemented by restarting the App, which is also determined by the class loading mechanism. As shown in the figure below, the repair method is called in BaseApplication after opening and loading the classes.dex file after the repair, as shown in the complete code