美团热修复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原理解析+并将框架代码从"闭源"变成"开源"(上篇)
上一篇: 沃尔玛也开始申请测试无人机运送包裹