An overview of the

As for Robust, the principle is simple and exquisite, but the details are endlessly complex. This article opens the door a bit to Robust through a handwritten basic version of RobustDemo. You can use this Demo to enrich the details of the Robust. The Demo in the making

Process is introduced



1. The original method injects Hook code

Use the Javassist+Gradle plug-in to inject hook code into all methods of the original class. Plug-in RobustInjectTransform. Groovy

Hook

ChangeQuickRedirect

Without a fix pack, changeQuickRedirect does not execute the isSupport() method. IsSupport also needs to identify which methods are patched.

package com.a.robust.patch;

public interface ChangeQuickRedirect {

     /** * If this method supports patches, then the patch content is distributed and executed. In the sub-interface implementation, the patch method of the patch class is called *@paramMethodParams Specifies the parameters * of the injected method@paramOriginObj Specifies the class of the injected method@paramIsStatic Whether a static method *@paramThe methodNumber method corresponds to the sequence number that we incremented during the injection process. Prepare for subsequent generation of automatic patches and running of patch code *@return* /
    Object accessDispatch(Object[] methodParams, Object originObj, boolean isStatic, int methodNumber);

    /** * whether this method supports patches *@param methodParams
     * @param originObj
     * @param isStatic
     * @param methodNumber
     * @return* /
    boolean isSupport(Object[] methodParams, Object originObj, boolean isStatic, int methodNumber);
}
Copy the code

Hook MethodMap

Also generate methodMap.txt

com.a.robust.data.M.a(int) :1
com.a.robust.data.M.b():2
Copy the code

2. Modify the original code

package com.a.robust.data;


import com.a.robust.patch.annotation.Modify;

public class M {
    private int apple = 3;

// public String a(int appleNum) {
// this.apple = appleNum;
// StringBuilder sb = new StringBuilder();
// sb.append("M aaa ,apple:");
// sb.append(this.apple);
// return sb.toString();
/ /}

    // Annotate the modified method, which will be needed in the subsequent auto-generated patch.
    @Modify
    public String a(int appleNum) {
        this.apple = appleNum + 1;
        StringBuilder sb = new StringBuilder();
        sb.append("M aaa fix,apple:");
        sb.append(this.apple);
        returnsb.toString(); }}Copy the code

Patch plug-ins are automatically generated

Plug-in RobustAutoPatchTransform. Groovy generated in the two classes

  • MPatch: Patch entity class, where the patch method is running.
  • MPatchControl: Implement the ChangeQuickRedirect interface and package MPatch.

You have to have two classes? And it isn’t. There is one more PatchProxy for actual Robust. Single MPatch, not impossible.

Generate the mpatch.class procedure

Use the Javassist + Gradle plugin.



The class is automatically generated by the patch plug-in. Here is the decompiled Java code

package com.a.robust.data;

import android.util.Log;
import com.a.robust.patch.EnhancedRobustUtils;

public class MPatch {
    M originClass;

    public MPatch(Object obj) {
        MPatch mPatch = this;
        this.originClass = (M) obj;
    }

    /** * this code corresponds to our Modify annotation method. * Translate method statements, line by line, into reflection using * and replace this in the code from MPatch to originClass, i.e. M. * /
    public String a(int i) {
        EnhancedRobustUtils.setFieldValue("apple".this instanceof MPatch ? r3.originClass : r3, i + 1, M.class);
        int d = Log.d("robust"."set value is apple No: 1");
        Object obj = null;
        Object obj2 = null;
        StringBuilder sb = (StringBuilder) EnhancedRobustUtils.invokeReflectConstruct("java.lang.StringBuilder".new Object[0].null);
        StringBuilder stringBuilder = sb;
        Object obj3 = null;
        M m = stringBuilder == this ? ((MPatch) stringBuilder).originClass : stringBuilder;
        Object[] objArr = new Object[1];
        Object[] objArr2 = objArr;
        objArr[0] = "M aaa fix,apple:";
        Class[] clsArr = new Class[1];
        Class[] clsArr2 = clsArr;
        clsArr[0] = String.class;
        StringBuilder stringBuilder2 = (StringBuilder) EnhancedRobustUtils.invokeReflectMethod("append", m, objArr2, clsArr2, StringBuilder.class);
        d = Log.d("robust"."invoke method is No: 2 append");
        StringBuilder stringBuilder3 = stringBuilder2;
        stringBuilder3 = sb;
        obj2 = null;
        int intValue = ((Integer) EnhancedRobustUtils.getFieldValue("apple".this instanceof MPatch ? r3.originClass : r3, M.class)).intValue();
        int d2 = Log.d("robust"."get value is apple No: 3");
        stringBuilder = stringBuilder3;
        obj3 = null;
        m = stringBuilder == this ? ((MPatch) stringBuilder).originClass : stringBuilder;
        objArr = new Object[1];
        objArr2 = objArr;
        Object[] objArr3 = objArr;
        Integer num = r16;
        Integer num2 = new Integer(intValue);
        objArr3[0] = num;
        clsArr = new Class[1];
        clsArr2 = clsArr;
        clsArr[0] = Integer.TYPE;
        stringBuilder2 = (StringBuilder) EnhancedRobustUtils.invokeReflectMethod("append", m, objArr2, clsArr2, StringBuilder.class);
        d = Log.d("robust"."invoke method is No: 4 append");
        stringBuilder3 = stringBuilder2;
        stringBuilder = sb;
        obj2 = null;
        String str = (String) EnhancedRobustUtils.invokeReflectMethod("toString", stringBuilder == this ? ((MPatch) stringBuilder).originClass : stringBuilder, new Object[0].null, StringBuilder.class);
        d = Log.d("robust"."invoke method is No: 5 toString");
        returnstr; }}Copy the code

Modify MethodMap

And I get the Modify mapping,

com.a.robust.data.M.a(int) :1
Copy the code

The final mapping string might look like this. It just includes the Modify method.

com.a.robust.data.M-->   1.3
com.a.robust.data.N-->   4.5
Copy the code

Generates the mPatchControl.class process

Use javassist+ Gradle plugin to generate according to Modify MethodMap and Hook MethodMap. The code that Javassist inserted, the code is pretty simple.

package com.a.robust.data;

import com.a.robust.patch.ChangeQuickRedirect;

public class MPatchControl implements ChangeQuickRedirect {

    public Object accessDispatch(Object[] methodParams, Object originObj, boolean isStatic, int methodNumber) {
        MPatch mPatch;
        if (isStatic) {
            mPatch = new MPatch(null);
        } else {
            mPatch = new MPatch(originObj);
        }
        if (1 == methodNumber) {
            return mPatch.a(((Integer) methodParams[0]).intValue());
        }
        return null;
    }

    public boolean isSupport(Object[] methodParams, Object originObj, boolean isStatic, int methodNumber) {
        return ": 1.".contains(new StringBuffer.append(":").append(methodNumber).append(":").toString()); }}}Copy the code

All Patch Class To Dex

MPatch. Class, MPatchControl. Class by dx – dex instructions generated dex, we named robust_patch. Dex

Patch Class Map

Classmap.txt records the original class and its ChangeQuickRedirect implementation

com.a.robust.data.M:com.a.robust.data.MPatchControl
Copy the code

Load the patch

We put the files generated above into assets,

  • robust_patch.dex
  • classMap.txt
package com.a.robust.patchexecute;

import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.a.robust.patch.EnhancedRobustUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

import dalvik.system.DexClassLoader;

public class PatchExecutor {
    /** * do the actual patch loading, and the code is super simple *@param context
     */
    public static void doPatch(Context context) {
        try {

            File classMapFile = copyAssetsToExternalCache(context, "classMap.txt");
            File patchFile = copyAssetsToExternalCache(context, "robust_patch.dex");
            Map<String, String> classMap = generatePatchClassMap(context, classMapFile);
            ClassLoader classLoader = generateClassLoader(context, patchFile);

            /** * classMap--> [com.a.robust.data.M:com.a.robust.data.MPatchControl] */
            for (Map.Entry<String, String> entry : classMap.entrySet()) {
                Class originClass = classLoader.loadClass(entry.getKey());
                Class patchControlClass = classLoader.loadClass(entry.getValue());
                /** * Reflection call, MPatchControl object set to the static variable m.changequickredirect */
                EnhancedRobustUtils.setStaticFieldValue(
                    "changeQuickRedirect",originClass,patchControlClass.newInstance()); }}catch(Exception e) { e.printStackTrace(); }}/** * Patch dex for classLoader, parent for context.getClassLoader() *@param context
     * @param patchFile
     * @return* /
    private static ClassLoader generateClassLoader(Context context, File patchFile) {
        return new DexClassLoader(
                patchFile.getAbsolutePath(),
                patchFile.getParentFile().getAbsolutePath(),
                null, context.getClassLoader());
    }

    /** * Copy from Assets to external cache to simulate patch download *@param context
     * @param assetFileName
     * @return* /
    private static File copyAssetsToExternalCache(Context context, String assetFileName);

    / * * * the classMap. TXT back into the Map - > [com. A.r obust. Data. M: com. A.r obust. Data. MPatchControl] * unlike robust, robust is generated, stored Map *@param context
     * @param classMapFile
     * @return* /
    public static Map<String, String> generatePatchClassMap(Context context, File classMapFile);
}

Copy the code

MainActivity

public class MainActivity extends AppCompatActivity {
    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_robust);
        context	= MainActivity.this.getApplicationContext();
        showText();
        findViewById(R.id.btnHotfix).setOnClickListener(v -> {
            PatchExecutor.doPatch(context);
            showText();

        });
    }

    private void showText(a) {
        String str = new M().a(100) + ","+ M.b(); ((TextView) findViewById(R.id.tvText)).setText(str); }}Copy the code

insufficient

If you want to understand the process, the above code is enough. But the actual method inlining, calling the super method, lambda expressions, and bridge methods are not considered. Demo is currently not supported. There is even hybrid compilation for AndroidN, which is not supported.