美团热更新大致原理基于三个步骤:
生成Apk文件时对代码进行插桩(编译期)
生成补丁包(编译期)
获取补丁信息并应用(应用期)
基于美团热更新0.4.72
插桩
例如
public int getCount()
{ while (this.list == null) { return i;
} return this.list.size();
}上面的代码会被插桩成这样:
public int getCount()
{ int i = 0;
ChangeQuickRedirect localChangeQuickRedirect = changeQuickRedirect;
Class localClass = Integer.TYPE; if (PatchProxy.isSupport(new Object[0], this, localChangeQuickRedirect, false, 924, new Class[0], localClass))
{
localChangeQuickRedirect = changeQuickRedirect;
localClass = Integer.TYPE;
i = ((Integer)PatchProxy.accessDispatch(new Object[0], this, localChangeQuickRedirect, false, 924, new Class[0], localClass)).intValue();
} while (this.list == null) { return i;
} return this.list.size();
}可以看到Robust为每个class增加了个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑.通过判断
PatchProxy.isSupport(new Object[0], this, localChangeQuickRedirect, false, 924, new Class[0], localClass)
这行代码是否为真来判断是否执行补丁包的逻辑。当为真的时候可能会执行到accessDispatch从而替换掉之前老的逻辑,达到fix的目的。所以只要初始化类时替换掉ChangeQuickRedirect变量就可以达到修复的目的
生成补丁包
当使用生成补丁包插件时会生成三个文件(XXX为对应修改的类名):
PatchesInfoImpl.java
XXXPatch.java
XXXPatchControl.java
其中:PatchesInfoImpl.java标记着所有修改的类的完整类名以及对应的傀儡补丁类的类名信息,也就是这里的XXXPatchConTrol.java的完整类名。如下
public class PatchesInfoImpl
implements PatchesInfo{ public List getPatchedClassesInfo()
{
ArrayList localArrayList = new ArrayList();
localArrayList.add(new PatchedClassInfo("com.xxx.xxx.TestAnswerActivity", "com.xxx.xxx.robust.patch.TestAnswerActivityPatchControl"));
com.meituan.robust.utils.EnhancedRobustUtils.isThrowable = false; return localArrayList;
}
}XXXPatch.java 包含着一个待应用的类中的方法,比如要修改A.jva 中的getInt()方法,XXXPatch就会包含A类中修改后的getInt()的方法
public class APatch{
A originClass;
public APatch(Object paramObject)
{ this.originClass = ((A)paramObject);
}
public Object[] getRealParameter(Object[] paramArrayOfObject)
{ if ((paramArrayOfObject == null) || (paramArrayOfObject.length < 1)) { return paramArrayOfObject;
}
Object[] arrayOfObject = new Object[paramArrayOfObject.length]; int i = 0; if (i < paramArrayOfObject.length)
{ if (paramArrayOfObject[i] == this) {
arrayOfObject[i] = this.originClass;
} for (;;)
{
i += 1; break;
arrayOfObject[i] = paramArrayOfObject[i];
}
} return arrayOfObject;
}
public void getInt()
{ //修改后的代码逻辑
...
}
}XXXPatchControl.java 是一个傀儡类,通过这个类的accessDispatch来调用到被修改的代码逻辑也就是XXXPatch.java 中的对应的类中的方法代码逻辑,如下:
public class APatchControl
implements ChangeQuickRedirect{ public static final String MATCH_ALL_PARAMETER = "(\\w*\\.)*\\w*"; private static final Map<Object, Object> keyToValueRelation = new WeakHashMap();
private static Object fixObj(Object paramObject)
{
Object localObject = paramObject; if ((paramObject instanceof Byte)) { if (((Byte)paramObject).byteValue() == 0) { break label32;
}
}
label32: for (boolean bool = true;; bool = false)
{
localObject = new Boolean(bool); return localObject;
}
}
public Object accessDispatch(String paramString, Object[] paramArrayOfObject)
{ try
{
APatch localAPatch; if (paramString.split(":")[2].equals("false")) { if (keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]) == null)
{
localAPatch = new APatch(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
keyToValueRelation.put(paramArrayOfObject[(paramArrayOfObject.length - 1)], null); break label134;
}
} while ("3134".equals(paramString.split(":")[3]))
{
localAPatch.getInt(); return null;
localAPatch = (APatch)keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]); break label134;
localAPatch = new localAPatch(null);
}
} catch (Throwable paramString)
{
paramString.printStackTrace(); return null;
}
label134: return null;
}
public Object getRealParameter(Object paramObject)
{
Object localObject = paramObject; if ((paramObject instanceof TestAnswerActivity)) {
localObject = new APatch(paramObject);
} return localObject;
}
public boolean isSupport(String paramString, Object[] paramArrayOfObject)
{ return "3134:".contains(paramString.split(":")[3]);
}
}应用时动态加载补丁包并应用
这时我们的apk需要修改线上bug时,拿到补丁包后,会进行动态加载path.dex文件,获取到PatchesInfoImpl.class,然后通过这个对象的getPatchedClassesInfo函数知道需要打补丁的是哪个哪个类,然后再通过反射获取到对应的补丁类,再替换掉需要打补丁的类里的ChangeQuickRedirect变量,就可以达到修复的目的
作者:AIl_Blue
链接:https://www.jianshu.com/p/29b9c44749a2