Android熱補丁之Robust(三)坑和解

在前兩篇文章中,分析了 Android 熱補丁框架 Robust 中,幾個重要的流程包括:java

  • 補丁加載過程
  • 基礎包插樁過程
  • 補丁包自動化生成過程

本篇文章主要分析下集成過程當中遇到的坑以及分析問題的思路和最終的解決方案。包含:android

  • 打補丁包出錯?
  • Robust 定義的 API 不夠用怎麼辦?
  • 插件 Plugin Transform 的順序問題?
  • 與 Aspectj 衝突怎麼辦?
  • static 方法中包含 super 方法怎麼辦?

系列文章:git

打補丁包出錯?

在打補丁包過程當中,碰到了一個錯誤 execute command java -jar /Users/wanglinglong/Develop/u51/Credit51/CreditCardManager/robust/dx.jar --dex --output=classes.dex meituan.jar error,找了一大圈最後發現是jdk老版本在Mac上的一個bug,升級jdk就行了,參考 Class JavaLaunchHelper is implemented in two placesgithub

Robust 定義的 API 不夠用怎麼辦?

Robust 提供了一些 API 可供開發者擴展使用,好比: 添加類庫依賴 compile 'com.meituan.robust:robust:0.4.82',其中 PatchManipulateImp 類的一些可擴展方法編程

protected List<Patch> fetchPatchList(Context context);

protected boolean verifyPatch(Context context, Patch patch);
    
protected boolean ensurePatchExist(Patch patch);
複製代碼

可是在一些狀況下,這些可擴展方法並不能知足咱們的需求。api

爲了知足定製化需求,能夠棄用 com.meituan.robust:robust,本身實現一套補丁加載邏輯,這個實現起來難度並不太大,主要補丁加載流程均可以參考 Robust 官方實現,具體加載邏輯可參考本系列第一篇文章,這裏再也不深刻。數組

插件 Plugin Transform 的順序問題?

首先,要找到 Gradle Plugin 編譯過程當中對於自定義 Transform 的處理,具體流程讀者能夠自行搜索查看,這裏只給出關鍵代碼app

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);
                            }
                        });
    }
    ...
}
複製代碼

在生成 Dex 任務以前,會處理全部的自定義 Transform 任務,邏輯是按照順序遍歷而後處理任務依賴關係,那麼從這裏咱們能夠知道,Transform 的執行順序是按照插件的聲明順序來執行的,也就是說,哪一個 plugin 的聲明在前,其對應的 Transform 就在前執行,舉個例子:框架

apply plugin: 'pluginA'
apply plugin: 'pluginB'
複製代碼

那麼對應的 TransformA 任務就會先於 TransfromB 任務執行。oop

好了,知道了 Transform 的執行順序問題,再來看下 Robust 插件的順序問題。首先來看下基線包的處理插件 apply plugin: 'robust',其邏輯是在每一個方法中前置插入補丁加載邏輯代碼,用於攔截基線包中的原有邏輯,達到修復方法的目的。 若是 robust Plugin 是先於其餘插件執行的,那麼會出現 Robust 插入代碼後,再執行其餘插件的代碼邏輯,這樣會有問題嗎?其實要具體問題具體分析,可能會有問題,也可能沒有問題。舉個例子,咱們項目中使用了聽雲,並且是較老的版本(2.5.9),其插件內部插入代碼沒有用到 Transform,而是用了另一種技術,其會致使無論在哪裏聲明插件,其處理順序都是最後執行,那麼對於 Robust 基線包插件來講,當基線包插件插入完代碼後,又會去執行聽雲插件的插入代碼邏輯,因此可能會看到如下這種代碼:

實際上,咱們最終想要的結果是這樣的:

然而,若是仔細觀察這種錯誤的代碼插入邏輯,實際上並無對最終的熱修復邏輯產生影響。是由於在生成補丁包過程當中,他們的執行順序也是這樣的,即聽雲插件最後執行,這樣的結果就是Robust 自動化補丁插件在生成插件後就強制中止了整個編譯流程,聽雲插件根本就沒有機會執行。因此最後補丁包中僅僅會包含剔除了Robust 基線包插件插入的代碼以及聽雲插件插入的代碼。能夠理解爲第一張圖中把紅框下面的代碼經過熱修復的方式移入了紅框裏面,而後return。

對於聽雲來講,2.8.x版本以後,插入代碼的邏輯也由 Tranform 來執行,也就是說,對於聽雲來講,不論是怎麼樣的執行順序,都不會與 Robust 發生兼容性問題。

因此仍是要具體問題具體分析,這裏僅僅提供一些排查問題的思路。

與 Aspectj 衝突怎麼辦?

咱們項目中大量使用了 AOP 技術,涉及到的框架有 Aspectj、javassist、ASM。其中因爲 javassist 和 ASM 徹底是有本身控制的,因此不會有問題,而對於 Aspectj 來講就沒這麼簡單了。

剛開始介入就碰到了這樣一個問題 Caused by: java.lang.ClassCastException: com.meituan.robust.patch.MainFragmentActivityPatch cannot be cast to com.zhangdan.app.activities.MainFragmentActivity,是一個類型強轉錯誤。

問題分析,因爲 Aspectj 框架爲開發者省略了不少邏輯,開發者只須要編寫切面相關代碼便可,因此須要梳理清楚 Aspectj 的原理:

首先貼下未混淆的代碼:

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 void onCreate_aroundBody1$advice(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint, MainFragmentActivity$$Injector mainFragmentActivity$$Injector, ProceedingJoinPoint proceedingJoinPoint) {
        if (!PatchProxy.proxy(new Object[]{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();
        }
    }
複製代碼

對應的patch 文件:

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");
    }
}
複製代碼

咱們往下跟下 Aspectj 的調用流程。

AjcClosure 類
public 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;
        return jp;
    }
}
複製代碼

AjcClosure 接收一個 Object 的對象數組,在基礎包中,它的實現是

new Object[]{this, bundle, makeJP}
複製代碼

注意這個 this,表明的是MainFragmentActivity 對象。 相對應的看下patch包中的實現

Object[] objArr = new Object[]{this, savedInstanceState, joinPoint};
複製代碼

這裏調用了下 getRealParameter(new Object[]{objArr}) 進行了 this 轉換,因此這裏的this 也是MainFragmentActivity對象,這裏是沒問題的。 而後調用 linkClosureAndJoinPoint 方法獲得 ProceedingJoinPoint 對象,當作參數傳遞給 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();
  }
複製代碼

調用了 ProceedingJoinPoint.proceed 抽象方法,實如今

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

這裏的 arc 是 AroundClosure,arc.getState() 返回的是構造 AroundClosure 時傳遞過來的對象數組。 最後調用了抽象方法 run(Object[] args),實如今

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;
    }
}
複製代碼

最後調用 MainFragmentActivity.onCreate_aroundBody2((MainFragmentActivity) objArr2[0], (Bundle) objArr2[1], (JoinPoint) objArr2[2]);,整個 AOP 的流程就走通了。

最後總結下 Aspectj 的調用流程:

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();
複製代碼

最後的 MainFragmentActivity.onCreate_aroundBody0();方法實際上就是onCreate()的原始方法邏輯。

另外,對於修改後的代碼,沒有被打入補丁,也是能夠解釋的。 對於 auto-path-plugin,Transform 的順序是 Aspectj -> auto-patch. 那麼,對於標記修改的 onCreate 方法來講,Aspectj 處理完後,onCreate 方法被替換成了代理,真正的方法實現被新生成的方法隱藏起來了。 而咱們僅僅標記了舊的 onCreate 方法,其結果就是,Aspectj 的代理 onCreate 方法被 patch 了,而實際的方法雖然方法體內有咱們的修復,可是因爲沒有標記 @modify 而被忽略。

由於是強轉 crash,因此在 $$Injector 代碼中插入一些 Log

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, new Class[]{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();
        }
    }
複製代碼

在不加載補丁狀況下的 log 以下:

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 
複製代碼

加載補丁後,log 以下:

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 
複製代碼

states[0] : MainFragmentActivityPatch 這個明顯是不對的,因此咱們知道了緣由,是由於在構造 AroundClosure 時候傳進來的參數不對。 報錯地方對應於上面的分析,也就是

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});
複製代碼

結果就是,把一個含有3個對象的一維數據,編程了含有一個對象的二維數組,而後去 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;
    }
複製代碼

而這個方法只判斷了一維數組的狀況,沒有判斷二維或多維數組的狀況。終於找到緣由了 😃 對應的修改方法:

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;
    }
複製代碼

castException 終因而搞定了。具體解決方法見 merge request #259

static 方法中包含 super 方法怎麼辦?

看到這個標題可能會一臉懵逼,static 方法中怎麼可能包含 super 調用?別急慢慢往下看。

書接上節,至於被 Aspectj 處理過的方法沒法被打入 patch 的問題,理論上來講跟泛型的橋方法是相似的,解決方案也是 @Modify -> RobustModify.modify();,修改後經驗證,會報錯。log以下

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
複製代碼

問題分析,根據堆棧顯示,這裏是在作替換 super 方法的邏輯,跟了下 plugin 的 debug,生成須要 replace 的 javassist 代碼爲 {staticRobustonCreate(this,originClass,$$);},而後在 replace 後,javac 編譯這條語句的時候跪了。 分析下須要替換的 super 的方法,這個方法其實是 Aspectj 處理後的方法,根據上面分析的 Aspectj 的調用流程得知,該方法其實是 onCreate 方法原始的邏輯,反編譯出來以下:

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);//這裏是須要替換的地方
            ...
        }
    }
複製代碼

而 auto-patch 作的工做是將super.onCreate方法包裝成 static 方法,正常生成的patch代碼以下:

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);
    }
}
複製代碼

而對於已經被 Aspectj 處理過的方法,是這樣的:

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);//這裏是須要替換的地方
            staticRobustonCreate(this,originClass,bundle);
            ...
        }
    }
複製代碼

在static 方法中使用了 this關鍵字,固然編譯出錯啦。同理這個 originClass 也不能夠出現,由於它是非 static 變量。 因爲 xxPatchRobustAssist.staticRobustonCreate() 方法並無用到前兩個變量(patch, activity),直接傳 null 行不行呢?經驗證是不行的,緣由以下。 看了下生成xxxPatchRobustAssist類的代碼:

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("}");
        ...
        return assistClass;
    }
}
複製代碼

實際上,最終生成的調用是 xxPatch.superMethod($$); ,$$表明所有參數。對於與上面的 onCreate 方法就是 xxPatch.onCreate(bundle);。 因此,patch 應該不能傳 null 了,不然運行時會報空指針,那第二個參數 activity 能不能傳 null 呢?繼續往下看。 首先,根據常識,static 方法中確定是不能調用 super方法的。從最終生成的代碼也能看出,這並非最終反編譯出的的 super.onCreate(bundle)方法調用。因此處理的地方確定在javassist修改編譯以後,對應處理的地方在smali 層,代碼:

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 new RuntimeException(e);
                    }
                ...
    }
複製代碼

實際上就是把方法調用從 invoke-invoke-virtual {p0, p2}, Lcom/meituan/robust/patch/SecondActivityPatch;->onCreate(Landroid/os/Bundle;)V 轉換成 invoke-super {p1, p2}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V,這步處理完纔會真正的調用父類的super方法。 也就是說,在 smali 處理完後,參數從 p0 -> p1,也就是參數從 xxpatch 換成了 Activity,第二個參數會在運行時用到,因此也不能傳null。 分析完了總結下,第二個參數 originClass 確定不能傳 null,不然會空指針;第一個參數 xxPatch,因爲在 smali 被替換成了第二個參數,因此有多是能夠傳 null 的。

解決方案:

  1. 修改 originClass 爲static,並新增一個 static patch 變量
  2. 因爲目前已是在 static 方法中存在 super 方法,對應的 smali 代碼:
.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;)V
複製代碼

因此只要不處理就行了,須要作的就是在 auto-plugin 中增長條件判斷,符合 static 方法中帶有 super 的不處理,一共有三處,一處是生成 xxPatchRobustAssist 輔助類,第二處在 javassit 替換 super 方法,第三處在 smali 處理補丁中的 super 方法。

對於方案1,問題: 若是 patch 和 originClass 都是 static,那麼就會有內存泄露的風險。 而且若是被 patch 方法是 static 方法,那麼在初始化 patch 時,originClass 會傳 null。

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);
複製代碼

一樣應該也是爲了不內存泄露,每修復一個方法就會生成一個 patch 對象並持有 static 的 originClass 引用。 對於方案2 ,問題: 首先,Aspectj 在 static 方法中插了個 super 方法(猜想也是在 smali 層作的修改),直接寫的話 javac 編譯時會報錯,smali 處理吧還沒到這一步。因此被修復後,這個 static 方法是在 xxPatch 類中的,auto-patch 即便不處理,運行時也不能正常運行,由於 xxPatch 不是 originClass 父類的子類,不能直接其調用 super 方法。

觀察 Aspectj 生成的方法,全部 的static 方法,第一個參數都是當前類的引用,好比 private static final void onCreate_aroundBody0(MainFragmentActivity mainFragmentActivity, Bundle bundle, JoinPoint joinPoint) {。 因此好比根據上面的分析,得出一個可行的方案:

def static String invokeSuperString(MethodCall m, String originClass) {
           ...
           stringBuilder.append(getStaticSuperMethodName(m.methodName) + "(" + null + "," + originClass + ",\$\$);");
           ...
    }
複製代碼

若是是 static 方法中含有 super 方法,就以下處理。 第一個 xxPatch 對象傳空,最後在 smali 處理的時候會被替換掉。 第二個參數是從相似onCreate_aroundBody0()中傳過來的,後面的是其餘參數。 最終結果:

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);
    }
複製代碼

到這裏這個問題就分析完了,詳細解決方發見 merge request #265

總結

這個系列到這裏基本就結束了。這篇文章主要介紹了在接入 Robust 過程當中碰到的一些坑以及解決思路,其實根本仍是熟讀源碼,碰到問題學習從源碼中找答案。要堅信,坑踩的多了,也就不怕坑了。最後福利一張。

相關文章
相關標籤/搜索