美團熱修復Robust-源碼篇


  上一篇主要分析了Robust的使用方法,這一篇就來總結一下Robust的源碼分析。

  我個人傾向於將Robust框架分為兩個部分,自動插入代碼和動態加載Patch。

一、Robust源碼分析

  目前我的分析將Robust動態加載分為兩個部分,一部分是插樁后的代碼邏輯,一部分是拉取Patch的邏輯。

  我們首先來看插樁后的代碼(這里面套用的是官方的代碼,可能有些過時了)

  插樁前

public long getIndex() {
    return 100;
}

  插樁后

public static ChangeQuickRedirect changeQuickRedirect;
    public long getIndex() {
        if(changeQuickRedirect != null) {
            //PatchProxy中封裝了獲取當前className和methodName的邏輯,並在其內部最終調用了changeQuickRedirect的對應函數
            if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
                return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
            }
        }
    return 100L;
}

  我們可以看到Robust為我們的類添加了一個靜態的ChangeQuickRedirect對象,我們可以看到當ChangeQuickRedirect為空時,證明此時沒有補丁,走原邏輯。當它不為空時,我們可以看到它調用了PatchProxy中的isSupport方法和accessDispatch方法。我們具體來看一下PatchProxy中的這兩個方法。

  

 1   public static boolean isSupport(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
 2         //Robust補丁優先執行,其他功能靠后
 3         if (changeQuickRedirect == null) {
 4             //不執行補丁,輪詢其他監聽者
 5             if (registerExtensionList == null || registerExtensionList.isEmpty()) {
 6                 return false;
 7             }
 8             for (RobustExtension robustExtension : registerExtensionList) {
 9                 if (robustExtension.isSupport(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType))) {
10                     robustExtensionThreadLocal.set(robustExtension);
11                     return true;
12                 }
13             }
14             return false;
15         }
16         String classMethod = getClassMethod(isStatic, methodNumber);
17         if (TextUtils.isEmpty(classMethod)) {
18             return false;
19         }
20         Object[] objects = getObjects(paramsArray, current, isStatic);
21         try {
22             return changeQuickRedirect.isSupport(classMethod, objects);
23         } catch (Throwable t) {
24             return false;
25         }
26     }

  我們可以看到第22行,它調用了changeQuickRedirect.isSupport方法,這個changeQuickRedirect便是我們注入的對象。

  我們接下來再看accessDispatch方法

 1  public static Object accessDispatch(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
 2 
 3         if (changeQuickRedirect == null) {
 4             RobustExtension robustExtension = robustExtensionThreadLocal.get();
 5             robustExtensionThreadLocal.remove();
 6             if (robustExtension != null) {
 7                 notify(robustExtension.describeSelfFunction());
 8                 return robustExtension.accessDispatch(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType));
 9             }
10             return null;
11         }
12         String classMethod = getClassMethod(isStatic, methodNumber);
13         if (TextUtils.isEmpty(classMethod)) {
14             return null;
15         }
16         notify(Constants.PATCH_EXECUTE);
17         Object[] objects = getObjects(paramsArray, current, isStatic);
18         return changeQuickRedirect.accessDispatch(classMethod, objects);

  可以看到第18行調用了changeQuickRedirect的accseeDispatch方法。

  注入后的代碼我們先看到這里,我們接下來看一看,我們拉取Patch的代碼

 1   new PatchExecutor(getApplicationContext(), new PatchManpulateImp(), new RobustCallBack() {
 2                     @Override
 3                     public void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches) {
 4                         Log.e("error-hot", "打印 onPatchListFetched:" + "isNet=" + isNet );
 5                     }
 6                     @Override
 7                     public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
 8                         Log.e("error-hot", "打印 onPatchFetched:" + "result=" + result+"isNet="+isNet + "--->" + "patch=" + patch);
 9                     }
10                     @Override
11                     public void onPatchApplied(boolean result, Patch patch) {
12                         Log.e("error-hot", "打印 onPatchApplied:" + "result=" + result + "--->" + "patch=" + patch);
13                     }
14                     @Override
15                     public void logNotify(String log, String where) {
16                         Log.e("error-hot", "打印 logNotify:" + "log=" + log + "--->" + "where=" + where);
17                     }
18                     @Override
19                     public void exceptionNotify(Throwable throwable, String where) {
20                         Log.e("error-hot", "打印 exceptionNotify:" + "throwable=" + throwable.toString() + "--->" + "where=" + where);
21                     }
22                 }).start();

  進入PatchExecutor類中看一看,我們可以發現它繼承了一個線程,那么直接去run方法看一下

 1    @Override
 2     public void run() {
 3         try {
 4             //拉取補丁列表
 5             List<Patch> patches = fetchPatchList();
 6             //應用補丁列表
 7             applyPatchList(patches);
 8         } catch (Throwable t) {
 9             Log.e("robust", "PatchExecutor run", t);
10             robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
11         }
12     }

  可以看到run方法中做了兩件事,拉取補丁列表和應用補丁列表。

  我們接着進入fetchPatchList方法

1    protected List<Patch> fetchPatchList() {
2         return patchManipulate.fetchPatchList(context);
3     }

  他返回了patchManipulate的fetchPatchList方法,這個對象便是我們在初始化的時候傳進來的。我們進入看一看

 1     @Override
 2     protected List<Patch> fetchPatchList(Context context) {
 3         Patch patch = new Patch();
 4         patch.setName("test patch");
 5         patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+
 6                 File.separator+"robust"+File.separator+"patch");
 7         patch.setPatchesInfoImplClassFullName("com.example.tyr.testrobust.PatchesInfoImpl");
 8         List<Patch> patches = new ArrayList<>();
 9         patches.add(patch);
10         return patches;
11     }

  我們將這個PatchesInfoImpl拉進到列表中,那么這個PatchInfoImpl是在哪里那?我們后面再說。

  接着看applyPatchList方法

  

protected void applyPatchList(List<Patch> patches) {
        if (null == patches || patches.isEmpty()) {
            return;
        }
        Log.d("robust", " patchManipulate list size is " + patches.size());
        for (Patch p : patches) {
            if (p.isAppliedSuccess()) {
                Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath());
                continue;
            }
            if (patchManipulate.ensurePatchExist(p)) {
                boolean currentPatchResult = false;
                try {
                    currentPatchResult = patch(context, p);
                } catch (Throwable t) {
                    robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69");
                }
                if (currentPatchResult) {
                    //設置patch 狀態為成功
                    p.setAppliedSuccess(true);
                    //統計PATCH成功率 PATCH成功
                    robustCallBack.onPatchApplied(true, p);

                } else {
                    //統計PATCH成功率 PATCH失敗
                    robustCallBack.onPatchApplied(false, p);
                }

                Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult);

            }
        }
    }

  可以看到for循環patches中的每一個patch並調用patch方法。我們接着進入patch方法。

 1 protected boolean patch(Context context, Patch patch) {
 2         if (!patchManipulate.verifyPatch(context, patch)) {
 3             robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
 4             return false;
 5         }
 6 
 7         DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
 8                 null, PatchExecutor.class.getClassLoader());
 9         patch.delete(patch.getTempPath());
10 
11         Class patchClass, oldClass;
12 
13         Class patchsInfoClass;
14         PatchesInfo patchesInfo = null;
15         try {
16             Log.d("robust", "PatchsInfoImpl name:" + patch.getPatchesInfoImplClassFullName());
17             patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
18             patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();
19             Log.d("robust", "PatchsInfoImpl ok");
20         } catch (Throwable t) {
21             robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:108");
22             Log.e("robust", "PatchsInfoImpl failed,cause of" + t.toString());
23             t.printStackTrace();
24         }
25 
26         if (patchesInfo == null) {
27             robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
28             return false;
29         }
30 
31         //classes need to patch
32         List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
33         if (null == patchedClasses || patchedClasses.isEmpty()) {
34             robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
35             return false;
36         }
37 
38         for (PatchedClassInfo patchedClassInfo : patchedClasses) {
39             String patchedClassName = patchedClassInfo.patchedClassName;
40             String patchClassName = patchedClassInfo.patchClassName;
41             if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
42                 robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
43                 continue;
44             }
45             Log.d("robust", "current path:" + patchedClassName);
46             try {
47                 oldClass = classLoader.loadClass(patchedClassName.trim());
48                 Field[] fields = oldClass.getDeclaredFields();
49                 Log.d("robust", "oldClass :" + oldClass + "     fields " + fields.length);
50                 Field changeQuickRedirectField = null;
51                 for (Field field : fields) {
52                     if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
53                         changeQuickRedirectField = field;
54                         break;
55                     }
56                 }
57                 if (changeQuickRedirectField == null) {
58                     robustCallBack.logNotify("changeQuickRedirectField  is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
59                     Log.d("robust", "current path:" + patchedClassName + " something wrong !! can  not find:ChangeQuickRedirect in" + patchClassName);
60                     continue;
61                 }
62                 Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
63                 try {
64                     patchClass = classLoader.loadClass(patchClassName);
65                     Object patchObject = patchClass.newInstance();
66                     changeQuickRedirectField.setAccessible(true);
67                     changeQuickRedirectField.set(null, patchObject);
68                     Log.d("robust", "changeQuickRedirectField set sucess " + patchClassName);
69                 } catch (Throwable t) {
70                     Log.e("robust", "patch failed! ");
71                     t.printStackTrace();
72                     robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
73                 }
74             } catch (Throwable t) {
75                 Log.e("robust", "patch failed! ");
76                 t.printStackTrace();
77                 robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
78             }
79         }
80         Log.d("robust", "patch finished ");
81         return true;
82     }

  可以看到我們的類加載器在這里加載了我們的patch,我們接下來可以看到classloader加載了我們的PatchInfoImpl類。在這個類中繼承了Robust的PatchInfo接口,這里只有一個方法

1 public interface PatchesInfo {
2     List<PatchedClassInfo> getPatchedClassesInfo();
3 }  

  他拉取了我們需要修改的類的信息。

  這里面的PatchedClassInfo中保存了兩個類的信息,一個是我們需要修改的類PatchedClass和修改他的類PatchClass。

  第39,40行Robust拿到了這兩個類的名字。

  第48行通過反射獲取了我們需要修改的類的所有field

  接下來是一個for循環獲取到我們注入代碼中的靜態ChangeQuickRedirect對象。

  獲取到對象后我們看第64行他加載了我們PatchClass的類

  接下來的65,66,67三行,我們可以看到他通過反射將我們PatchedClass即oldClass中的changeQuickRedirect字段賦值為我們的PatchClass。至於這個PatchClass是什么。我們接下來說。

  到目前為止,我們可以看到,插樁后的邏輯已經說完了,不得不說Robust的原理還是比較通俗易懂的。我們接下來回答前面的兩個剩余問題,PatchInfoImpl和PatchClass在哪里。我們順着我們的Patch.jar去尋找。反編譯后得到如下列表。

  

  找到了我們的PatchesInfoImpl,而我們的PatchClass就是RobustActivityPatchControl了

  我們先來看一看PatchesInfoImpl做了什么

 1 import com.meituan.robust.PatchedClassInfo;
 2 import com.meituan.robust.PatchesInfo;
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class PatchesInfoImpl
 7   implements PatchesInfo
 8 {
 9   public List getPatchedClassesInfo()
10   {
11     ArrayList localArrayList = new ArrayList();
12     localArrayList.add(new PatchedClassInfo("com.example.tyr.testrobust.RobustActivity", "com.example.tyr.testrobust.RobustActivityPatchControl"));
13     com.meituan.robust.utils.EnhancedRobustUtils.isThrowable = false;
14     return localArrayList;
15   }
16 }

  可以看到他把我們的patchedClass和patchClass加入了list中,也就是上面返回的信息。

  我們接着看我們注入的這個patchClass中的方法

 1 public class RobustActivityPatchControl
 2   implements ChangeQuickRedirect
 3 {
 4   public static final String MATCH_ALL_PARAMETER = "(\\w*\\.)*\\w*";
 5   private static final Map<Object, Object> keyToValueRelation = new WeakHashMap();
 6 
 7   private static Object fixObj(Object paramObject)
 8   {
 9     Object localObject = paramObject;
10     if ((paramObject instanceof Byte))
11       if (((Byte)paramObject).byteValue() == 0)
12         break label32;
13     label32: for (boolean bool = true; ; bool = false)
14     {
15       localObject = new Boolean(bool);
16       return localObject;
17     }
18   }
19 
20   public Object accessDispatch(String paramString, Object[] paramArrayOfObject)
21   {
22     label134: 
23     while (true)
24       try
25       {
26         if (!paramString.split(":")[2].equals("false"))
27           continue;
28         if (keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]) != null)
29           continue;
30         RobustActivityPatch localRobustActivityPatch = new RobustActivityPatch(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
31         keyToValueRelation.put(paramArrayOfObject[(paramArrayOfObject.length - 1)], null);
32         break label134;
33         if (!"12".equals(paramString.split(":")[3]))
34           break;
35         localRobustActivityPatch.onCreate((Bundle)paramArrayOfObject[0]);
36         return null;
37         localRobustActivityPatch = (RobustActivityPatch)keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
38         break label134;
39         localRobustActivityPatch = new RobustActivityPatch(null);
40         continue;
41       }
42       catch (Throwable paramString)
43       {
44         paramString.printStackTrace();
45         return null;
46       }
47     return null;
48   }
49 
50   public Object getRealParameter(Object paramObject)
51   {
52     Object localObject = paramObject;
53     if ((paramObject instanceof RobustActivity))
54       localObject = new RobustActivityPatch(paramObject);
55     return localObject;
56   }
57 
58   public boolean isSupport(String paramString, Object[] paramArrayOfObject)
59   {
60     paramString = paramString.split(":")[3];
61     return ":12:".contains(":" + paramString + ":");
62   }
63 }

  我們可以看到它實現了Robust的ChangeQuickRedirect接口,並實現了他們的兩個方法accessDispatch和isSupport的兩個方法,也就是PatchProxy中調用的這兩個方法。

  先說isSupport方法

  這里的isSupport方法是混淆后的,我們可以看到在PatchProxy類中,他傳入了classMethod的名字和這個方法所需要的參數。校驗后進行判斷。

  在PatchProxy中他傳入了的classMethod格式為className:methodName:isStatic:methodNumber。這里只校驗了方法的number。這里是在accessDispatch中傳入,目測插樁后的代碼有所改動。

  繼續說accessPatch方法。

  第26行校驗了是否為靜態方法。將參數數組傳給了RobustActivityPatch這個類,並調用了它的onCreat方法,莫名的熟悉感,這個就是我們標注為Modify標簽的那個類。

  我們接下來看一看RobustActivityPatch這個類

  

 1 import android.os.Bundle;
 2 import android.support.v7.app.c;
 3 import android.view.View;
 4 import android.widget.TextView;
 5 import com.meituan.robust.utils.EnhancedRobustUtils;
 6 
 7 public class RobustActivityPatch
 8 {
 9   RobustActivity originClass;
10 
11   public RobustActivityPatch(Object paramObject)
12   {
13     this.originClass = ((RobustActivity)paramObject);
14   }
15 
16   public static void staticRobustonCreate(RobustActivityPatch paramRobustActivityPatch, RobustActivity paramRobustActivity, Bundle paramBundle)
17   {
18     RobustActivityPatchRobustAssist.staticRobustonCreate(paramRobustActivityPatch, paramRobustActivity, paramBundle);
19   }
20 
21   public Object[] getRealParameter(Object[] paramArrayOfObject)
22   {
23     if ((paramArrayOfObject == null) || (paramArrayOfObject.length < 1))
24       return paramArrayOfObject;
25     Object[] arrayOfObject = new Object[paramArrayOfObject.length];
26     int i = 0;
27     if (i < paramArrayOfObject.length)
28     {
29       if ((paramArrayOfObject[i] instanceof Object[]))
30         arrayOfObject[i] = getRealParameter((Object[])paramArrayOfObject[i]);
31       while (true)
32       {
33         i += 1;
34         break;
35         if (paramArrayOfObject[i] == this)
36         {
37           arrayOfObject[i] = this.originClass;
38           continue;
39         }
40         arrayOfObject[i] = paramArrayOfObject[i];
41       }
42     }
43     return arrayOfObject;
44   }
45 
46   protected void onCreate(Bundle paramBundle)
47   {
48     staticRobustonCreate(this, this.originClass, paramBundle);
49     EnhancedRobustUtils.invokeReflectMethod("setContentView", ((RobustActivityPatch)this).originClass, getRealParameter(new Object[] { new Integer(2131296284) }), new Class[] { Integer.TYPE }, c.class);
50     paramBundle = (View)EnhancedRobustUtils.invokeReflectMethod("findViewById", ((RobustActivityPatch)this).originClass, getRealParameter(new Object[] { new Integer(2131165307) }), new Class[] { Integer.TYPE }, c.class);
51     if (paramBundle == this);
52     for (paramBundle = ((RobustActivityPatch)paramBundle).originClass; ; paramBundle = (TextView)paramBundle)
53     {
54       String str = (String)EnhancedRobustUtils.invokeReflectMethod("RobustPublicgetString", new RobustActivityInLinePatch(getRealParameter(new Object[] { this })[0]), getRealParameter(new Object[0]), null, null);
55       Object localObject = paramBundle;
56       if (paramBundle == this)
57         localObject = ((RobustActivityPatch)paramBundle).originClass;
58       EnhancedRobustUtils.invokeReflectMethod("setText", localObject, getRealParameter(new Object[] { str }), new Class[] { CharSequence.class }, TextView.class);
59       return;
60     }
61   }
62 }

  看到onCreate方法,第48行,這里我們可以看到他特殊處理了一下我們在RobustActivity的onCreate方法,感覺有點怪怪的,這里似乎是又執行了一邊RobustActivity的OnCreate方法,而不是super.onCreate。

  ps:看了看美團官方關於super的解析,似乎是這樣的,他通過調用RobustActivity的OnCreate,將class文件中的invokevirtual指令替換為invokesuper指令,從而達到super的效果,這里面還有個問題,如果這樣調用會出現這樣的問題

Caused by: java.lang.NoSuchMethodError: No super method thisIsSuper()V in class Lcom/meituan/sample/TestSuperClass; or its super classes (declaration of 'com.meituan.sample.TestSuperClass' appears in /data/app/com.meituan.robust.sample-3/base.apk)

  Robust的解決方案是使這個類也繼承RobustActivity的父類,我們可以看到RobustActivityPatchRobustAssist類果然繼承了一個類,但是由於混淆我們看到的是他繼承了一個c的類,猜測它應該就是RobustActivity的父類AppCompatActivity。

  驗證一下打印dex文件

  

  看到invoke-super指令,現在可以確定了

  再看一下RobustActivityPatchRobustAssist的父類,這絕對就是android.support.v7.app.AppcompatActivity了。

  

  然后我們可以看到他執行了setContentView,findViewById這里傳入的兩串數字便是我們的布局和空間在R類的數字。

  然后我們可以看到它執行到了我們修改代碼的地方。

  第54行它調用了RobustPublicgetString方法,又是莫名的熟悉感,,

  進入RobustActivityInLinePatch看一看。

 1 public class RobustActivityInLinePatch
 2 {
 3   RobustActivity originClass;
 4 
 5   public RobustActivityInLinePatch(Object paramObject)
 6   {
 7     this.originClass = ((RobustActivity)paramObject);
 8   }
 9 
10   private String getString()
11   {
12     return "hello robust";
13   }
14 
15   public String RobustPublicgetString()
16   {
17     return getString();
18   }
19 
20   public Object[] getRealParameter(Object[] paramArrayOfObject)
21   {
22     if ((paramArrayOfObject == null) || (paramArrayOfObject.length < 1))
23       return paramArrayOfObject;
24     Object[] arrayOfObject = new Object[paramArrayOfObject.length];
25     int i = 0;
26     if (i < paramArrayOfObject.length)
27     {
28       if ((paramArrayOfObject[i] instanceof Object[]))
29         arrayOfObject[i] = getRealParameter((Object[])paramArrayOfObject[i]);
30       while (true)
31       {
32         i += 1;
33         break;
34         if (paramArrayOfObject[i] == this)
35         {
36           arrayOfObject[i] = this.originClass;
37           continue;
38         }
39         arrayOfObject[i] = paramArrayOfObject[i];
40       }
41     }
42     return arrayOfObject;
43   }
44 }

  可以看到我們傳入的getString方法出現在了這里。

二、總結

  到目前為止,Robust的邏輯算是走通了。

  目前為止,我認為Robust的核心應該算是它自動插樁的那一部分,目前暫時不涉及了,下一篇將會了解一下熱修復背后的動態加載。

參考資料:

  Android中熱修復框架Robust原理解析+並將框架代碼從"閉源"變成"開源"(上篇)

  Android熱更新方案Robust

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM