Android Hot Repair: Andfix and Hotfix, Comparison and Implementation of Two Solutions

Keywords: Android PHP Gradle SDK

android thermal repair technology I think the earliest should be QQ Space Team The solution, which was really needed later, was carefully investigated. Among the current solutions, Ali has two kinds of Dexposed and Dexposed. Andfix framework Because the former one does not support android system above 5.0, we will look at Andfix as the solution of Ali Department. Hotfix framework It is the realization of QQ space team theory mentioned above. The purpose of this paper is to write the implementation scheme and the principle.

Andfix

Introduce

Framework official website: https://github.com/alibaba/AndFix 
The introduction is written in English, so the translation website is attached.
http://blog.csdn.net/qxs965266509/article/details/49802429

Using android studio development, the following are introduced:

compile 'com.alipay.euler:andfix:0.4.0@aar'

principle

The following is a process diagram of the repair for our better understanding.

As you can see, andfix fixes are method-level, replacing bug ged methods.

Patch

There are official ways to use it, but it is relatively simple, so there will be some modifications. My idea is to make patches, then put them on the server, the client downloads the patches to the specified folder, and then fixes them.  
Firstly, there must be patch making tools, and the authorities are ready for us. Here 
After decompression, we put the pre-repaired APK and the repaired apk, keystore (for convenience, I used the debug keystore) into this folder, as follows:
 
Among them, we need to use commands to make patch files. That is to say, we need a pre-repair APK to compare with the post-repair apk. The meaning of the command is as follows:

Command: apkpatch.bat-f new.a P K-T old.a P k-o output 1-k debug.keystore-p android-a Android debugkey-e Android

- F <new.apk>: new version
 - t <old.apk>: Old version
 - O < output >: Output directory
 - K < keystore >: keystore for packaging
 - P < password >: keystore password
 - a <alias>: keystore user alias
 - e <alias password>: keystore user alias password

 
Then a file with a suffix of. apatch is generated in outputdic:
 
Renamed out.apatch. That's our patch.

Patch up

How to use patches? It's the same procedure as putting an elephant in a refrigerator.  
The following code goes directly to:
Step 1: Put the patch on the server.  
For simplicity, use xampp, write a section of php code, play a download function on it.

<?php
$file_name = "out.apatch";//Files to be downloaded
define("SPATH","/files/");//Relative paths for storing files
$file_sub_path = $_SERVER['DOCUMENT_ROOT'];//Absolute address of website root directory
$file_path = $file_sub_path.SPATH.$file_name;//The absolute address of the file, the first three connections
//Determine whether a file exists
if(!file_exists($file_path)){
 echo "This file does not exist";
 return;
}
$fp = fopen($file_path,"r");//Open file
$file_size = filesize($file_path);//Get file size
/*
*header for downloading files
*/
header("Content-type:application/octet-stream");
header("Accept-Ranges:bytes");
header("Accept-Length:".$file_size);
header("Content-Disposition:attachment;filename=".$file_name);

$buffer=1024;
$file_count=0;
//Return data to browser
while(!feof($fp) && $file_count<$file_size){
 $file_con = fread($fp,$buffer);
 $file_count += $buffer;
 echo $file_con;//If you don't echo here, you will only download 0 bytes of files.
}
fclose($fp);
?>

Step 2: Download and patch.  
Back to android, in our application:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        YuanAndfix.inject(this);
    }
}

Among them, the YuanAndfix class:

public class YuanAndfix {
    public static final String apatch_path = "out.apatch";
    public static void inject(final Context context) {

        final PatchManager patchManager = new PatchManager(context);
        patchManager.init(BuildConfig.VERSION_CODE + "");//current version
        patchManager.loadPatch();
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpDownload httpDownload = new HttpDownload();
                httpDownload.downFile("http://192.168.1.12/download.php", context.getDir("patch", Context.MODE_PRIVATE).getAbsolutePath()+"/",apatch_path);
                try {
                    String patchPath =context.getDir("patch", Context.MODE_PRIVATE).getAbsolutePath()+"/"+apatch_path;
                    File file = new File(patchPath);
                    if (file.exists()) {
                        patchManager.addPatch(patchPath);
                        Toast.makeText(context,"Complete patching",Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(context,"fail",Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

In this way, the hot repair is completed. My example is to click on the button and pop up toast to display the text. Before the repair, it is

Toast.makeText(MainActivity.this,"bug",Toast.LENGTH_SHORT).show();

After repairing, it is:

Toast.makeText(MainActivity.this,"fixed",Toast.LENGTH_SHORT).show();

The above is the use of Andfix. After my experiment, the limitation of using this framework is that it can not modify global variables and add new methods, but it can modify existing methods and add local variables. From this point of view, Andfix actually requires that we only modify the bug s in the method, not make large-scale changes. If we feel that this kind of repair can not meet the repair requirements, then we can see another kind of hot repair scheme with less limitations.

HotFix

principle

Official website: https://github.com/dodola/HotFix 
Before using this framework, I want you to take a look at it first. principle It is very helpful for the later implementation.

Let me briefly explain the principle.

When android is loaded by inserting multiple dex files into the classloader of app, if there are the same classes in multiple dex files, the preceding classes will be loaded. So the principle of this hotfix is to replace the problematic classes and put the required classes in the front to achieve the purpose of hotfix.  
 
But there is a problem, we want to replace the class, can not be marked CLASS_ISPREVERIFIED flag, otherwise the return error, so the difficulty of this solution is how to make the class that want to be repaired not be marked CLASS_ISPREVERIFIED flag. So the hack magic trick of the gods came. First we made an dex package, then we injected the dex package into the construction method of the class we wanted to repair. In fact, it is a class that outputs the dex package.
System.out.println(dodola.hackdex.AntilazyLoad.class); 
In this way, the class we want to fix can be loaded without the CLASS_ISPREVERIFIED flag.

frame

The use of this framework is relatively cumbersome in terms of configuration and patch generation, although there is a similar framework Nuwa Automation was done, but some pits were said to be unfilled, so the hotfix framework was used decisively. Download the framework, let's take a look at the structure first.  
 
app is the main project;
BuilSrc is Gradle's Task. Gradle's compilation command is composed of many tasks. In other words, Gradle will execute the commands in the order of these tasks when compiling the program.  
There is an empty class in hackdex for compilation, so that the class of the main project is not marked with CLASS_ISPREVERIFIED.  
hotfixlib is a repair tool class.

Next, let's see how they work together.  
First, the main project app build.gradle file, which contains two more pieces of code:

task('processWithJavassist') << {
    String classPath = file('build/intermediates/classes/debug')//The directory where the project compiler class is located
    dodola.patch.PatchClass.process(classPath, project(':hackdex').buildDir
            .absolutePath + '/intermediates/classes/debug')//The second parameter is the class directory of hackdex

}

and

applicationVariants.all { variant ->
        variant.dex.dependsOn << processWithJavassist //Type the code into the class before executing the dx command
    }

This is to inject the construction method of the class of the main project through javassist.
System.out.println(dodola.hackdex.AntilazyLoad.class); 
AntilazyLoad.class in app assets, the program will be copied to the sd card after running, mainly in order to make the main project class not be marked CLASS_ISPREVERIFIED.

Patch

A patch is a collection of class files of classes that you want to replace. The process of patch making is a reference.
https://github.com/dodola/HotFix; 
The classes used here are advanced:
 
Then put the repaired classes in a folder. The folder path must be the same as the package name of your original class. Such as:
For example, the BugClass.class class class shown above is placed in such a folder.
 
Then execute the command:
 
This generates a path.jar under the d disk, and then the jar is made into dex jar. Because dx is used, and this dx is in our sdk toolkit, so I copy this path.jar to the sdk toolkit, using the dx command.
 
 
Then path_dex.jar will be generated, which is our patch file.

Patch up

public class HotfixApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        try {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Then download and patch

      switch (item.getItemId()) {
            case R.id.action_fix: {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String url = "http://192.168.1.12/download.php";
                        HttpDownload httpDownload = new HttpDownload();
                        final int flag = httpDownload.downFile(url, MainActivity.this.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath()+"/", "path_dex.jar");
                        HotFix.patch(MainActivity.this, MainActivity.this.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath()+"/"+"path_dex.jar", "");
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                String fileState=null;
                                if (flag==0) {
                                    fileState = "Download complete";
                                } ;
                                if (flag==1) {
                                    fileState = "file already exist";
                                }
                                if (flag==-1) {
                                    fileState = "Download error";
                                }
                                Toast.makeText(MainActivity.this, fileState, Toast.LENGTH_SHORT).show();
                            }
                        });

                    }
                }).start();
            }
            break;
            case R.id.action_test:
                LoadBugClass bugClass = new LoadBugClass();
                Toast.makeText(this, "Test call method:" + bugClass.getBugString(), Toast.LENGTH_SHORT).show();
                break;
        }

It is important to note that once a class has been invoked, the next startup patch is required to take effect. So if we click on the test first and then download, then we need to restart the program (background killing) before the patch will take effect.

Manual injection

The above method of preventing classes from being marked with CLASS_ISPREVERIFIED is good, but it has limitations. We have to compile with gradle and understand bytecode injection. If we develop with eclipse, it won't work. In fact, we have another way to add that line to classes manually.
System. out. println (dododola. hackdex. AntilazyLoad. class) code, as long as the compilation is guaranteed to pass. So here we go. We're building a new project, Android studio.
 
Look at main, we have created a new hack folder with a hack.jar in it. There is only one class in it:

public class AntilazyLoad {
}

Then, in our main project app, the class construction method is added.
System. out. println (dododola. hackdex. AntilazyLoad. class), this line of code, to achieve the purpose of manual injection, do not need those complex task code, byte code injection and other operations. So if you use eclipse, that's the directory.
 
This jar package will not be packaged into app, that is to say, the real AntilazyLoad.class is actually hack_dex.jar under the assets package of the project.  
All the above methods are completely feasible, especially the manual injection method, which can solve the problem that most developers will not use heat more. I'm looking at this approach. This article Learned.  
PS:  
1. This framework can't be modified by final modification. Keep in mind.  
2. The patch code given by the official website

HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.BugClass");

It's unreasonable to see that the third parameter should pass the name of the bug class. We can't predict which class will have a bug, so I changed it to this way.

HotFix.patch(this, dexPath.getAbsolutePath(), "");

The third parameter is no longer needed. It's easy to test it by yourself.

summary

Compared with the two solutions, Ali's andfix pays more attention to detail bug s. Although it works from the native layer, the framework is well encapsulated. It's easy to use and has new maintenance. It is said that Ali's app plans to use this. If we are just developing an app without major changes, hotter global variables, and no additional methods, then this framework is the first choice.  
But sometimes we may develop a sdk, such as allied sdk, or want to heat up more global variables, add methods, then andfix can not be used, so hotfix is a better choice at this time.  
Download point here
Andfixdemo 
HotFixdemo 
Server-side PHP code

Posted by swampster on Sat, 30 Mar 2019 09:51:29 -0700