上一篇主要分析了Robust的使用方法,這一篇就來總結一下Robust的源碼分析。 我個人傾向於將Robust框架分為兩個部分,自動插入代碼和動態載入Patch。 一、Robust源碼分析 目前我的分析將Robust動態載入分為兩個部分,一部分是插樁後的代碼邏輯,一部分是拉取Patch的邏輯。 我們 ...
上一篇主要分析了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原理解析+並將框架代碼從"閉源"變成"開源"(上篇)