In the first two articles, several important processes in the Android hotpatch framework Robust were analyzed, including:

  • Patch Loading Process
  • Foundation pile insertion process
  • Automatic generation of patch packages

This article mainly analyzes the pits encountered in the integration process and the ideas of analyzing the problems and the final solution. Contains:

  • Failed to install patch package?
  • What if the API defined by Robust is not enough?
  • Plugin Plugin Transform order problem?
  • What about conflicts with Aspectj?
  • What if a static method contains a super method?

Series of articles:

  • Robust Principle Analysis of Android Hot Patch
  • Robust analysis of Android Hot Patch (2)
  • Robust (3) pit settlement for Android Hot patch

Failed to install patch package?

In the process of installing the patch package, Met a wrong the execute command Java jar/Users/wanglinglong/Develop/u51 / Credit51 / CreditCardManager/robust/dx. Jar – dex –output=classes.dex meituan.jar error –output=classes.dex meituan.jar error –output=classes.dex meituan.jar error –output=classes.dex meituan.jar error See Class JavaLaunchHelper is Implemented in two places

What if the API defined by Robust is not enough?

Robust provides some API for developers to extend the use of, such as: add libraries depend on the compile ‘com. At meituan. Robust: Robust: 0.4.82’, some PatchManipulateImp type extensible method

protected List<Patch> fetchPatchList(Context context);

protected boolean verifyPatch(Context context, Patch patch);
    
protected boolean ensurePatchExist(Patch patch);
Copy the code

However, in some cases, these extensible approaches do not meet our needs.

In order to meet the demand of customized, can be abandoned com. At meituan. Robust: robust, oneself for a patch loading logic, the difficulty to implement is not too big, the main patch loading process can be a reference robust official implementation, specific loading logic can refer to the first article in this series, here no longer.

Plugin Plugin Transform order problem?

First of all, we need to find out how the Gradle Plugin handles the custom Transform in the compilation process

TaskManager.java
/** * Creates the post-compilation tasks for the given Variant. * * These tasks create the dex file from the .class files, plus optional intermediary steps like * proguard and jacoco */
public void createPostCompilationTasks(
        @NonNull final VariantScope variantScope) {...// ---- Code Coverage first -----.// Merge Java Resources.
    createMergeJavaResTransform(variantScope);
    // ----- External Transforms -----
    // apply all the external transforms.
    List<Transform> customTransforms = extension.getTransforms();
    List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
    for (int i = 0, count = customTransforms.size(); i < count; i++) {
        Transform transform = customTransforms.get(i);
        List<Object> deps = customTransformsDependencies.get(i);
        transformManager
                .addTransform(taskFactory, variantScope, transform)
                .ifPresent(
                        t -> {
                            if(! deps.isEmpty()) { t.dependsOn(deps); }// if the task is a no-op then we make assemble task depend on it.
                            if(transform.getScopes().isEmpty()) { variantScope.getAssembleTask().dependsOn(t); }}); }... }Copy the code

Before the Dex task is generated, all custom Transform tasks are processed. The logic is traversed sequentially and then the task dependencies are processed. From this, we can know that the execution order of the Transform is executed according to the declaration order of the plug-in, that is, which plugin declaration comes first. The corresponding Transform is executed before, for example:

apply plugin: 'pluginA'
apply plugin: 'pluginB'
Copy the code

The TransformA task is executed before the TransfromB task.

Okay, now that we know the order of the Transform, let’s look at the order of the Robust plug-in. First, take a look at the baseline package processing plug-in Apply Plugin: ‘robust’, whose logic is to pre-insert the patch loading logic code in each method to intercept the original logic in the baseline package and achieve the purpose of repairing the method. If the robust Plugin is executed before the other plug-ins, then the code logic of the other plug-ins will be executed after the robust insertion. Is this a problem? In fact, a case-by-case analysis, there may be a problem, may not be a problem. For example, the auditory cloud was used in our project, and it was the older version (2.5.9). The internal insertion code of the plug-in did not use Transform, but used another technology, which would result in the processing order of the plug-in being executed last no matter where the plug-in was declared. So for the Robust baseline package plug-in, When the baseline package plug-in is done inserting code, it executes the cloud plug-in’s inserting code logic, so you might see code like this:

In fact, what we want in the end is this:

However, if you look closely at this faulty code insertion logic, it actually has no effect on the final hot fix logic. The reason is that in the process of generating patch package, their execution sequence is also like this, that is, listen to the cloud plug-in is executed last. As a result, the Robust automatic patch plug-in forcibly stops the whole compilation process after generating the plug-in, and listen to the cloud plug-in has no chance to execute at all. Therefore, the final patch package will only contain the code that removes the Robust baseline package plug-in and the code that listens to the cloud plug-in. In the first figure, the code below the red box is moved into the red box by hotfix, and then return.

For Listening cloud, after version 2.8.x, the logic of inserting code is also executed by Tranform. That is to say, for Listening cloud, no matter what the execution order is, there will be no compatibility problem with the Robust.

Therefore, it is still necessary to analyze the specific problems. Here are only some ideas for troubleshooting problems.

What about conflicts with Aspectj?

AOP is used extensively in our project, involving frameworks such as Aspectj, Javassist, and ASM. Javassist and ASM are completely under their own control, so there’s no problem, but with Aspectj it’s not that simple.

Just stepped in encountered such a problem under Caused by: Java. Lang. ClassCastException: Com. At meituan. Robust. Patch. MainFragmentActivityPatch always be cast to com. Zhangdan. App. Activities. MainFragmentActivity, This is a type force error.

Aspectj is a framework for developers to omit a lot of logic, developers only need to write relevant code, so we need to clarify the principle of Aspectj:

First post the unconfused code:

MainFragmentActivity.class
    static final void onCreate_aroundBody2(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint) { onCreate_aroundBody1$advice(mainFragmentActivity, bundle, joinPoint, MainFragmentActivity? Injector.aspectOf(), (ProceedingJoinPoint) joinPoint); }private static final voidonCreate_aroundBody1$advice(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint, MainFragmentActivity? Injector mainFragmentActivity? Injector, ProceedingJoinPoint proceedingJoinPoint) {if(! PatchProxy.proxy(newObject[]{mainFragmentActivity, bundle, joinPoint, mainFragmentActivity? Injector, proceedingJoinPoint},null, changeQuickRedirect, true.11888.new Class[]{MainFragmentActivity.class, Bundle.class, JoinPoint.class, MainFragmentActivity?Injector.class, ProceedingJoinPoint.class}, Void.TYPE).isSupported) {
            MainFragmentActivity mainFragmentActivity2 = (MainFragmentActivity) proceedingJoinPoint.getTarget();
            onCreate_aroundBody0(mainFragmentActivity, bundle, proceedingJoinPoint);
        }
    }

    public void onCreate(Bundle bundle) {
        if(! PatchProxy.proxy(new Object[]{bundle}, this, changeQuickRedirect, false.11849.new Class[]{Bundle.class}, Void.TYPE).isSupported) {
            JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, this.this, bundle); MainFragmentActivity? Injector.aspectOf().onCreate(new AjcClosure3(new Object[]{this, bundle, makeJP}).linkClosureAndJoinPoint(69648)); }}private static final void onCreate_aroundBody0(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint) {
        if(! PatchProxy.proxy(new Object[]{mainFragmentActivity, bundle, joinPoint}, null, changeQuickRedirect, true.11887.new Class[]{MainFragmentActivity.class, Bundle.class, JoinPoint.class}, Void.TYPE).isSupported) {
            super.onCreate(bundle);
            JudgeEmulatorUtil.uploadEmulatorInfoIfNeed(mainFragmentActivity);
            instance = mainFragmentActivity;
            mainFragmentActivity.setContentView(R.layout.main_activity);
            ButterKnife.bind((Activity) mainFragmentActivity);
            mainFragmentActivity.initUserCenterManager();
            mainFragmentActivity.mainPagerAdapter = new MainPagerAdapter(mainFragmentActivity, mainFragmentActivity.getSupportFragmentManager());
            mainFragmentActivity.userInfoPresenter = new UserInfoPresenter();
            mainFragmentActivity.refreshOldDataPresenter = new RefreshOldDataPresenter();
            mainFragmentActivity.tabRedPointPresenter = new TabRedPointPresenter(mainFragmentActivity);
            mainFragmentActivity.getMsgCenterRedPresenter = new GetMsgCenterRedPresenter(mainFragmentActivity);
            mainFragmentActivity.userInfoPresenter.setUserInfoView(mainFragmentActivity);
            mainFragmentActivity.userInfoPresenter.startGetCurUserInfoDBUseCase();
            INSTANCE_FLAG = 1; BaiduLocation.getInstance(ZhangdanApplication.getInstance()).start(); mainFragmentActivity.initToolBar(); mainFragmentActivity.showImportBillDialog(); mainFragmentActivity.onLoginCreate(bundle); mainFragmentActivity.getLoggerABConfig(); }}Copy the code

Corresponding patch file:

public class MainFragmentActivityPatch {
    MainFragmentActivity originClass;

    public MainFragmentActivityPatch(Object obj) {
        this.originClass = (MainFragmentActivity) obj;
    }

    protected void onCreate(Bundle savedInstanceState) {
        StaticPart staticPart = (StaticPart) EnhancedRobustUtils.getStaticFieldValue("ajc$tjp_0", MainFragmentActivity.class);
        Log.d("robust"."get static value is ajc$tjp_0 No: 1");
        JoinPoint joinPoint = (JoinPoint) EnhancedRobustUtils.invokeReflectStaticMethod("makeJP", Factory.class, getRealParameter(new Object[]{staticPart, this.this, savedInstanceState}), new Class[]{StaticPart.class, Object.class, Object.class, Object.class});
        Object obj = (Injector) EnhancedRobustUtils.invokeReflectStaticMethod("aspectOf", Injector.class, getRealParameter(new Object[0]), null);
        Object[] objArr = new Object[]{this, savedInstanceState, joinPoint};
        Log.d("robust"." inner Class new No: 2");
        Object obj2 = (AjcClosure3) EnhancedRobustUtils.invokeReflectConstruct("com.zhangdan.app.activities.MainFragmentActivity$AjcClosure3", getRealParameter(new Object[]{objArr}), new Class[]{Object[].class});
        if (obj2 == this) {
            obj2 = ((MainFragmentActivityPatch) obj2).originClass;
        }
        ProceedingJoinPoint proceedingJoinPoint = (ProceedingJoinPoint) EnhancedRobustUtils.invokeReflectMethod("linkClosureAndJoinPoint", obj2, getRealParameter(new Object[]{new Integer(69648)}), new Class[]{Integer.TYPE}, AroundClosure.class);
        Log.d("robust"."invoke method is No: 3 linkClosureAndJoinPoint");
        if (obj == this) {
            obj = ((MainFragmentActivityPatch) obj).originClass;
        }
        EnhancedRobustUtils.invokeReflectMethod("onCreate", obj, getRealParameter(new Object[]{proceedingJoinPoint}), new Class[]{ProceedingJoinPoint.class}, Injector.class);
        Log.d("robust"."invoke method is No: 4 onCreate"); }}Copy the code

Let’s follow the invocation flow of Aspectj.

AjcClosure classpublic abstract class AroundClosure {...protected Object[] state;

    public AroundClosure(Object[] state) {
    	this.state = state;
    }

    public Object[] getState() {
      return state;
    }

	/** * This takes in the same arguments as are passed to the proceed * call in the around advice (with primitives coerced  to Object types) */
    public abstract Object run(Object[] args) throws Throwable;

    public ProceedingJoinPoint linkClosureAndJoinPoint(int flags) {
        //TODO is this cast safe ?
        ProceedingJoinPoint jp = (ProceedingJoinPoint)state[state.length-1];
        jp.set$AroundClosure(this);
        this.bitflags = flags;
        returnjp; }}Copy the code

AjcClosure takes an array of objects, which in the base package is implemented as

new Object[]{this, bundle, makeJP}
Copy the code

Notice that this represents the MainFragmentActivity object. Take a look at the implementation in patch package accordingly

Object[] objArr = new Object[]{this, savedInstanceState, joinPoint};
Copy the code

GetRealParameter (New Object[]{objArr}) is called to convert this, so this is also a MainFragmentActivity Object, which is fine. Then call the linkClosureAndJoinPoint method to get the ProceedingJoinPoint object, which is passed as a parameter to MainFragmentActivity? Injector.onCreate(ProceedingJoinPoint joinPoint)

MainFragmentActivity? Injector.class@Aspect
public class MainFragmentActivity?Injector {
  @Around("execution(* com.zhangdan.app.activities.MainFragmentActivity.onCreate(..) )")
  public void onCreate(ProceedingJoinPoint joinPoint) throws Throwable { MainFragmentActivity target = (MainFragmentActivity)joinPoint.getTarget(); . joinPoint.proceed(); }Copy the code

Call the ProceedingJoinPoint. Proceed abstract methods, implementation in

    JoinPointImpl.java
	public Object proceed(a) throws Throwable {
		// when called from a before advice, but be a no-op
			return arc.run(arc.getState());
	}
Copy the code

AroundClosure arc is a AroundClosure, and arc.getState() returns the array of objects passed in to construct this AroundClosure. Finally, the abstract method run(Object[] args) is called


public class MainFragmentActivity$AjcClosure3 extends AroundClosure {
    public static ChangeQuickRedirect changeQuickRedirect;

    public MainFragmentActivity$AjcClosure3(Object[] objArr) {
        super(objArr);
    }

    public Object run(Object[] objArr) {
        PatchProxyResult proxy = PatchProxy.proxy(new Object[]{objArr}, this, changeQuickRedirect, false.11911.new Class[]{Object[].class}, Object.class);
        if (proxy.isSupported) {
            return proxy.result;
        }
        Object[] objArr2 = this.state;
        MainFragmentActivity.onCreate_aroundBody2((MainFragmentActivity) objArr2[0], (Bundle) objArr2[1], (JoinPoint) objArr2[2]);
        return null; }}Copy the code

The last call MainFragmentActivity. OnCreate_aroundBody2 ((MainFragmentActivity) objArr2 [0], (Bundle) objArr2 [1]. (JoinPoint) objArr2[2]); , the whole AOP process will go through.

Finally, the Aspectj invocation flow is summarized:

MainFragmentActivity.onCreate -> MainFragmentActivity? Injector.onCreate(ProceedingJoinPoint joinPoint) -> ProceedingJoinPoint.proceed() -> AroundClosure.run(Object[] args) ->  MainFragmentActivity$AjcClosure3.run(Object[] objArr) -> MainFragmentActivity.onCreate_aroundBody2((MainFragmentActivity) objArr2[0], (Bundle) objArr2[1], (JoinPoint) objArr2[2]); -> 
MainFragmentActivity.onCreate_aroundBody1$advice -> 
MainFragmentActivity.onCreate_aroundBody0();
Copy the code

The final MainFragmentActivity. OnCreate_aroundBody0 (); The method is actually the original method logic for onCreate().

In addition, it can be explained that the modified code was not patched. For auto-path-plugin, the Transform sequence is Aspectj -> auto-patch. So, in the case of the token-modified onCreate method, after Aspectj is done, the onCreate method is replaced with a proxy, and the real method implementation is hidden by the newly generated method. As a result, Aspectj’s proxy onCreate method is patched, and the actual method, which has our fixes in it, is ignored without the @modify tag.

A. crash B. crash C. crash D. crash The Injector code inserted some logs

MainFragmentActivity? Injector.class@Around("execution(* com.zhangdan.app.activities.MainFragmentActivity.onCreate(..) )")
    public void onCreate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        if(! PatchProxy.proxy(new Object[]{proceedingJoinPoint}, this, changeQuickRedirect, false.11892.newClass[]{ProceedingJoinPoint.class}, Void.TYPE).isSupported) { MainFragmentActivity target = (MainFragmentActivity) proceedingJoinPoint.getTarget(); . Log.d("robust-wll"."test for aspectj start "); . Log.d("robust-wll"."getTarget : " + proceedingJoinPoint.getTarget().getClass().getSimpleName());
            Log.d("robust-wll"."getThis : " + proceedingJoinPoint.getThis().getClass().getSimpleName());
            Log.d("robust-wll"."getArgs[0] : " + (proceedingJoinPoint.getArgs()[0] != null ? proceedingJoinPoint.getArgs()[0].getClass().getSimpleName() : null));
            Field arcField = proceedingJoinPoint.getClass().getDeclaredField("arc");
            arcField.setAccessible(true);
            AroundClosure arc = (AroundClosure) arcField.get(proceedingJoinPoint);
            if(! (arc ==null || arc.getState() == null || arc.getState().length < 3)) {
                Object[] states = arc.getState();
                Log.d("robust-wll"."states[0] : " + (states[0] != null ? states[0].getClass().getSimpleName() : null));
                Log.d("robust-wll"."states[1] : " + (states[1] != null ? states[1].getClass().getSimpleName() : null));
                Log.d("robust-wll"."states[2] : " + (states[2] != null ? states[2].getClass().getSimpleName() : null));
            }
            Log.d("robust-wll"."test for aspectj end "); proceedingJoinPoint.proceed(); }}Copy the code

Log without patch loading is as follows:

04-03 17:13:41.184 15469 15469 D robust-wll: test for aspectj start 
04-03 17:13:41.184 15469 15469 D robust-wll: getTarget : MainFragmentActivity
04-03 17:13:41.185 15469 15469 D robust-wll: getThis : MainFragmentActivity
04-03 17:13:41.185 15469 15469 D robust-wll: getArgs[0] : null
04-03 17:13:41.185 15469 15469 D robust-wll: states[0] : MainFragmentActivity
04-03 17:13:41.185 15469 15469 D robust-wll: states[1] : null
04-03 17:13:41.186 15469 15469 D robust-wll: states[2] : JoinPointImpl
04-03 17:13:41.186 15469 15469 D robust-wll: test for aspectj end 
Copy the code

After the patch is loaded, the log is as follows:

04-03 17:27:41.885 18490 18490 D robust-wll: test for aspectj start 
04-03 17:27:41.885 18490 18490 D robust-wll: getTarget : MainFragmentActivity
04-03 17:27:41.886 18490 18490 D robust-wll: getThis : MainFragmentActivity
04-03 17:27:41.886 18490 18490 D robust-wll: getArgs[0] : null
04-03 17:27:41.886 18490 18490 D robust-wll: states[0] : MainFragmentActivityPatch
04-03 17:27:41.886 18490 18490 D robust-wll: states[1] : null
04-03 17:27:41.886 18490 18490 D robust-wll: states[2] : JoinPointImpl
04-03 17:27:41.886 18490 18490 D robust-wll: test for aspectj end 
Copy the code

States [0] : MainFragmentActivityPatch this is obviously not right, so we know the reason, because when constructing AroundClosure incoming parameters is not correct. The error corresponds to the analysis above, i.e

        Object[] objArr = new Object[]{this, savedInstanceState, joinPoint};
        Object obj2 = (AjcClosure3) EnhancedRobustUtils.invokeReflectConstruct("com.zhangdan.app.activities.MainFragmentActivity$AjcClosure3", getRealParameter(new Object[]{objArr}), new Class[]{Object[].class});
Copy the code

The result is to program a one-dimensional array of three objects into a two-dimensional array of one, and then getRealParameter.

    public Object[] getRealParameter(Object[] objArr) {
        if (objArr == null || objArr.length < 1) {
            return objArr;
        }
        Object[] objArr2 = new Object[objArr.length];
        for (int i = 0; i < objArr.length; i++) {
            if (objArr[i] == this) {
                objArr2[i] = this.originClass;
            } else{ objArr2[i] = objArr[i]; }}return objArr2;
    }
Copy the code

This method only evaluates one-dimensional arrays, not two-dimensional or multidimensional arrays. Finally found the reason 😃 corresponding modification method:

    public Object[] getRealParameter(Object[] objArr) {
        if (objArr == null || objArr.length < 1) {
            return objArr;
        }
        Object[] objArr2 = new Object[objArr.length];
        for (int i = 0; i < objArr.length; i++) {
            if (objArr[i] instanceof Object[]) {
                objArr2[i] = getRealParameter(((Object[]) objArr[i]));
            } else {
                if (objArr[i] == this) {
                    objArr2[i] = this.originClass;
                } else{ objArr2[i] = objArr[i]; }}}return objArr2;
    }
Copy the code

CastException is finally done. See Merge Request #259 for a solution.

What if a static method contains a super method?

How can a static method contain a super call? Take your time and look down.

Modify -> robustmodify.modify (); After modification, an error will be reported. The log is as follows

Caused by: javassist.CannotCompileException: [source error] not-available: this
        at javassist.expr.MethodCall.replace(MethodCall.java:241)
        at javassist.expr.MethodCall$replace$2.call(Unknown Source)
        at com.meituan.robust.autopatch.PatchesFactory$1.edit(PatchesFactory.groovy:144)
        at javassist.expr.ExprEditor.loopBody(ExprEditor.java:224)
        at javassist.expr.ExprEditor.doit(ExprEditor.java:91)
        at javassist.CtBehavior.instrument(CtBehavior.java:712)
        at javassist.CtBehavior$instrument$1.call(Unknown Source)
        at com.meituan.robust.autopatch.PatchesFactory.createPatchClass(PatchesFactory.groovy:76)
        at com.meituan.robust.autopatch.PatchesFactory.createPatch(PatchesFactory.groovy:310)
        at com.meituan.robust.autopatch.PatchesFactory$createPatch.call(Unknown Source)
        at robust.gradle.plugin.AutoPatchTransform.generatPatch(AutoPatchTransform.groovy:190)
        at robust.gradle.plugin.AutoPatchTransform$generatPatch$0.callCurrent(Unknown Source)
        at robust.gradle.plugin.AutoPatchTransform.autoPatch(AutoPatchTransform.groovy:138)
        at robust.gradle.plugin.AutoPatchTransform$autoPatch.callCurrent(Unknown Source)
        at robust.gradle.plugin.AutoPatchTransform.transform(AutoPatchTransform.groovy:97)
        at com.android.build.api.transform.Transform.transform(Transform.java:290)
        at com.android.build.gradle.internal.pipeline.TransformTask$2.call(TransformTask.java:185)
        at com.android.build.gradle.internal.pipeline.TransformTask$2.call(TransformTask.java:181)
        at com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:102)...27 more
Caused by: compile error: not-available: this
        at javassist.compiler.CodeGen.atKeyword(CodeGen.java:1908)
        at javassist.compiler.ast.Keyword.accept(Keyword.java:35)
        at javassist.compiler.JvstCodeGen.atMethodArgs(JvstCodeGen.java:358)
        at javassist.compiler.MemberCodeGen.atMethodCallCore(MemberCodeGen.java:569)
        at javassist.compiler.MemberCodeGen.atCallExpr(MemberCodeGen.java:537)
        at javassist.compiler.JvstCodeGen.atCallExpr(JvstCodeGen.java:244)
        at javassist.compiler.ast.CallExpr.accept(CallExpr.java:46)
        at javassist.compiler.CodeGen.atStmnt(CodeGen.java:338)
        at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
        at javassist.compiler.CodeGen.atStmnt(CodeGen.java:351)
        at javassist.compiler.ast.Stmnt.accept(Stmnt.java:50)
        at javassist.compiler.Javac.compileStmnt(Javac.java:569)
        at javassist.expr.MethodCall.replace(MethodCall.java:235)...45 more
Copy the code

Problem analysis, according to the stack display, here is doing the logic to replace the super method, followed by the debug of plugin, The javassist code to generate replace is {staticRobustonCreate(this,originClass,?). ; }, and then after replace, javac compiles the statement. Analyze the super method that needs to be replaced. This method is actually processed by Aspectj. According to the above analysis of Aspectj calling process, this method is actually the original logic of onCreate method, decompiled as follows:

    private static final void onCreate_aroundBody0(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint) {
        if(! PatchProxy.proxy(new Object[]{mainFragmentActivity, bundle, joinPoint}, null, changeQuickRedirect, true.11887.new Class[]{MainFragmentActivity.class, Bundle.class, JoinPoint.class}, Void.TYPE).isSupported) {
            super.onCreate(bundle);// This is where the replacement is needed. }}Copy the code

The auto-patch method wraps the super.onCreate method as static. The patch code normally generated is as follows:

public class SecondActivityPatch {
    SecondActivity originClass;

    public SecondActivityPatch(Object obj) {
        this.originClass = (SecondActivity) obj;
    }
    public void onCreate(Bundle bundle) {
        staticRobustonCreate(this,originClass,bundle); . }public static void staticRobustonCreate(SecondActivityPatch secondActivityPatch, SecondActivity secondActivity, Bundle bundle) {
        SecondActivityPatchRobustAssist.staticRobustonCreate(secondActivityPatch, secondActivity, bundle);
    }

public class SecondActivityPatchRobustAssist extends Activity {
    public static void staticRobustonCreate(SecondActivityPatch secondActivityPatch, SecondActivity secondActivity, Bundle bundle) {
        super.onCreate(bundle); }}Copy the code

For methods already handled by Aspectj, it looks like this:

    private static final void onCreate_aroundBody0(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint) {
        if(! PatchProxy.proxy(new Object[]{mainFragmentActivity, bundle, joinPoint}, null, changeQuickRedirect, true.11887.new Class[]{MainFragmentActivity.class, Bundle.class, JoinPoint.class}, Void.TYPE).isSupported) {
            //super.onCreate(bundle); // This is where the replacement is needed
            staticRobustonCreate(this,originClass,bundle); . }}Copy the code

Use this keyword in static method, of course compilation error. Likewise, originClass can’t happen because it’s a non-static variable. Because xxPatchRobustAssist. StaticRobustonCreate () method is not used in the first two variables (patch, the activity), direct pass null line not line? Empirically it doesn’t, and here’s why. Take a look at the code that generates the xxxPatchRobustAssist class:

class PatchesAssistFactory {
    def
    static createAssistClass(CtClass modifiedClass, String patchClassName, CtMethod removeMethod) {... StringBuilder staticMethodBuidler =new StringBuilder();
        if (removeMethod.parameterTypes.length > 0) {
            staticMethodBuidler.append("public static " + removeMethod.returnType.name + "" + ReflectUtils.getStaticSuperMethodName(removeMethod.getName())
                    + "(" + patchClassName + " patchInstance," + modifiedClass.getName() + " modifiedInstance," + JavaUtils.getParameterSignure(removeMethod) + "{");

        } else {
            staticMethodBuidler.append("public static " + removeMethod.returnType.name + "" + ReflectUtils.getStaticSuperMethodName(removeMethod.getName())
                    + "(" + patchClassName + " patchInstance," + modifiedClass.getName() + " modifiedInstance){");

        }
        staticMethodBuidler.append(" return patchInstance." + removeMethod.getName() + "(" + JavaUtils.getParameterValue(removeMethod.getParameterTypes().length) + ");");
        staticMethodBuidler.append("}"); .returnassistClass; }}Copy the code

In fact, the resulting call is xxpatch.supermethod (?). ; ,? Represents all parameters. For the onCreate method above is xxpatch.oncreate (bundle); . Patch cannot pass null; otherwise, the runtime will report a null pointer. Keep reading. First, it is common sense that you cannot call super in a static method. As you can see from the resulting generated code, this is not the final decompiled super.oncreate (bundle) method call. So the processing must be done in the SMali layer after javassist has compiled the changes:

SmaliTool.java
    private String invokeSuperMethodInSmali(final String line, String fullClassName) {... result = line.replace(Constants.SMALI_INVOKE_VIRTUAL_COMMAND, Constants.SMALI_INVOKE_SUPER_COMMAND);try {
                        if(! ctMethod.getReturnType().isPrimitive()) { returnType ="L" + ctMethod.getReturnType().getName().replaceAll("\ \."."/");
                        } else {
                            returnType = String.valueOf(((CtPrimitiveType) ctMethod.getReturnType()).getDescriptor());
                        }
                        if (NameManger.getInstance().getPatchNameMap().get(fullClassName).equals(fullClassName)) {
                            result = result.replace("p0"."p1"); }... }catch (Exception e) {
                        throw newRuntimeException(e); }... }Copy the code

Is actually the method call from invoke – invoke – virtual {p0, p2}, Lcom/at meituan/robust/patch/SecondActivityPatch; ->onCreate(Landroid/os/Bundle;) V is converted to invoke super {p1, p2}, Landroid/support/v7 / app/AppCompatActivity; ->onCreate(Landroid/os/Bundle;) V, the super method of the parent class is actually called after this step. After smALI is finished, the parameter is changed from p0 -> p1 to Activity. The second parameter is used at runtime, so null cannot be passed. The second argument originClass must not pass null, otherwise it will be null; The first parameter xxPatch is replaced by the second parameter in smali, so it is possible to pass null.

Solution:

  1. Change originClass to static and add a static patch variable
  2. Since the super method already exists in the static method, the corresponding smali code:
.method private static final onCreate_aroundBody0(Lcom/zhangdan/app/activities/MainFragmentActivity; Landroid/os/Bundle; Lorg/aspectj/lang/JoinPoint;)V
 ...
invoke-super {p0, p1}, Lcom/zhangdan/app/activities/WrapperAppCompatFragmentActivity; ->onCreate(Landroid/os/Bundle;) VCopy the code

Static method with super (); static method with super (); static method with super (); static method with super (); The second place is where Javassit replaces the super method, and the third place is where SmALI handles the super method in the patch.

For scheme 1, the problem is: if patch and originClass are static, there is a risk of memory leaks. And if the patch method is static, originClass will pass NULL when initializing the patch.

    public Object accessDispatch(String methodName, Object[] paramArrayOfObject) {
        try {
            Log.d("robust".new StringBuffer().append("arrivied in AccessDispatch ").append(methodName).append(" paramArrayOfObject ").append(paramArrayOfObject).toString());
            MainFragmentActivityPatch mainFragmentActivityPatch;
            if(! methodName.split(":") [2].equals("false")) {
                Log.d("robust"."static method forward ");
                mainFragmentActivityPatch = new MainFragmentActivityPatch(null);
Copy the code

Also to avoid memory leaks, a patch object is generated every time a method is fixed and holds a static originClass reference. For scenario 2, the problem is: First of all, Aspectj inserts a super method in the static method (which is probably the change made in the smali layer). Javac will compile an error if you write it directly. XxPatch is not a subclass of originClass, so you cannot call super directly from xxPatch.

Looking at Aspectj generated methods, all static methods take a reference to the current class as the first argument, Private static final void onCreate_aroundBody0(MainFragmentActivity MainFragmentActivity, Bundle Bundle, {JoinPoint JoinPoint). Therefore, according to the above analysis, a feasible solution can be obtained:

    def static String invokeSuperString(MethodCall m, String originClass) {... stringBuilder.append(getStaticSuperMethodName(m.methodName) +"(" + null + "," + originClass + ", \ \ $);"); . }Copy the code

If a static method has a super method, do as follows. The first xxPatch object is null and will be replaced when smALI processes it. The second parameter is passed in from something like onCreate_aroundBody0(), followed by the other parameters. Final result:

    public static void staticRobustonCreate(MainFragmentActivityPatch mainFragmentActivityPatch, MainFragmentActivity mainFragmentActivity, Bundle bundle) {
        MainFragmentActivityPatchRobustAssist.staticRobustonCreate(mainFragmentActivityPatch, mainFragmentActivity, bundle);
    }

    private static final void onCreate_aroundBody0(MainFragmentActivity ajc$this, Bundle savedInstanceState, JoinPoint joinPoint) {... staticRobustonCreate(null, ajc$this, savedInstanceState);
    }
Copy the code

This completes the analysis of the problem and the detailed solution is sent to Merge Request #265

conclusion

That’s pretty much the end of the series. This article mainly introduces some pits and solutions encountered in the process of accessing the Robust. In fact, I still read the source code thoroughly and learned to find answers from the source code when encountering problems. To believe that the pit step more, is not afraid of the pit. One last bonus.