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

w4lle發表於2019-02-28

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

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

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

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

系列文章:

打補丁包出錯?

在打補丁包過程中,碰到了一個錯誤 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 places

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

但是在一些情況下,這些可擴充套件方法並不能滿足我們的需求。

為了滿足定製化需求,可以棄用 com.meituan.robust:robust,自己實現一套補丁載入邏輯,這個實現起來難度並不太大,主要補丁載入流程都可以參考 Robust 官方實現,具體載入邏輯可參考本系列第一篇文章,這裡不再深入。

外掛 Plugin Transform 的順序問題?

首先,要找到 Gradle Plugin 編譯過程中對於自定義 Transform 的處理,具體流程讀者可以自行搜尋檢視,這裡只給出關鍵程式碼

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 任務執行。

好了,知道了 Transform 的執行順序問題,再來看下 Robust 外掛的順序問題。首先來看下基線包的處理外掛 apply plugin: 'robust',其邏輯是在每個方法中前置插入補丁載入邏輯程式碼,用於攔截基線包中的原有邏輯,達到修復方法的目的。 如果 robust Plugin 是先於其他外掛執行的,那麼會出現 Robust 插入程式碼後,再執行其他外掛的程式碼邏輯,這樣會有問題嗎?其實要具體問題具體分析,可能會有問題,也可能沒有問題。舉個例子,我們專案中使用了聽雲,而且是較老的版本(2.5.9),其外掛內部插入程式碼沒有用到 Transform,而是用了另外一種技術,其會導致不管在哪裡宣告外掛,其處理順序都是最後執行,那麼對於 Robust 基線包外掛來說,當基線包外掛插入完程式碼後,又會去執行聽雲外掛的插入程式碼邏輯,所以可能會看到以下這種程式碼:

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

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

Android熱補丁之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 過程中碰到的一些坑以及解決思路,其實根本還是熟讀原始碼,碰到問題學習從原始碼中找答案。要堅信,坑踩的多了,也就不怕坑了。最後福利一張。

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

相關文章