-
Notifications
You must be signed in to change notification settings - Fork 808
Robust代码结构
本文对Robust在代码层面进行分析,针对的代码版本是0.4.71,整体来说Robust热更新系统分为了四个模块,如下图:
-
autopatchbase
是整个项目的一个基础库,用于存放公共的代码 -
gradle-plugin
是Robust的插桩插件,会对指定的类插入静态字段ChangeQuickRedirect,对类的方法插入桩判定代码,是Robust的核心思想库 -
auto-patch-plugin
这个库是整个项目最复杂的一块,需要针对不同的代码风格、ReProguard补丁代码,生成一个可用的补丁。这部分的难点重点体现在Proguard对代码进行混淆之后,补丁中的代码也需要按照之前的Proguard规则来进行重新一次Proguard,我把这个过程称为ReProguard。这部分可以参考一下之前的博客:Android热更新方案Robust开源 -
patch
是补丁加载的核心,这部分控制了如何加载补丁,可以根据自己的需求定制化自己的补丁加载、校验等策略。
在详细介绍代码结构之前,我们先聊聊补丁文件的构成(由哪些类组成,以及每个类的功能是什么): 上图中的类可以分为六种:
-
PatchesInfoImpl
这个类是补丁的描述问题,在这个类里面的内容如下:
public class PatchesInfoImpl implements PatchesInfo {
public List getPatchedClassesInfo() {
List arrayList = new ArrayList();
arrayList.add(new PatchedClassInfo("com.meituan.sample.robusttest.l", "com.meituan.robust.patch.SampleClassPatchControl"));
arrayList.add(new PatchedClassInfo("com.meituan.sample.robusttest.p", "com.meituan.robust.patch.SuperPatchControl"));
arrayList.add(new PatchedClassInfo("com.meituan.sample.SecondActivity", "com.meituan.robust.patch.SecondActivityPatchControl"));
EnhancedRobustUtils.isThrowable = false;
return arrayList;
}
}
这个类使用到了一个结构体PatchedClassInfo
这个类的是混淆后的类名和补丁中转发器的映射关系,比如代码中的第一个结构体PatchedClassInfo
的内容,就是把类com.meituan.sample.robusttest.l
中的所有方法转发到com.meituan.robust.patch.SampleClassPatchControl
,xxPatchControl
类被我们称为转发器,负责把方法转发到对应的补丁方法。
-
xxPatchControl
以PatchControl
结尾的类被我们成为转发器,这个类的主要职责就是把APK中的某些方法转到补丁中的对应方法,我们可以先看看代码:public class SampleClassPatchControl implements ChangeQuickRedirect { private static final Map<Object, Object> keyToValueRelation; public boolean isSupport(String methodName, Object[] paramArrayOfObject) { return "66:".contains(methodName.split(":")[3]); } public Object accessDispatch(String methodName, Object[] paramArrayOfObject) { SampleClassPatch sampleClassPatch; if (methodName.split(":")[2].equals("false")) { SampleClassPatch sampleClassPatch2; if (keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]) == null) { sampleClassPatch2 = new SampleClassPatch(paramArrayOfObject[paramArrayOfObject.length - 1]); keyToValueRelation.put(paramArrayOfObject[paramArrayOfObject.length - 1], sampleClassPatch2); } else { sampleClassPatch2 = (SampleClassPatch) keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]); } sampleClassPatch = sampleClassPatch2; } else { sampleClassPatch = new SampleClassPatch(null); } if ("66".equals(methodName.split(":")[3])) { return Integer.valueOf(sampleClassPatch.multiple(((Integer) paramArrayOfObject[0]).intValue())); } return null; } }
首先会先调用
isSupport
方法,这个方法主要的职责是负责确定一个方法是否需要被转发,不被转发就意味着这个方法还是执行原始apk中的逻辑;如果被转发,则会调用accessDispatch
方法,accessDispatch
方法会对环境进行一些初始化之后(创建了一一对应的补丁类xxPatch的instance),调用补丁类中的响应方法。 tips:为什么isSupport
中是66
呢?这是因为我们对每个方法进行了编号,这是方法转发的依据,只有编号匹配才可以转发,这样就避免了方法签名的匹配。 -
xxPatch
这个结尾的类,这个是补丁类,包含了修复bug的全部逻辑代码,这部分代码比较多,这个包含的主要代码就是对改动类的一次翻译:把改动方法中调用的方法和字段访问全部改为了反射调用,同时要解决Proguard造成的混淆、以及内联的问题。这块属于自动化补丁的重点和难点,有意请多多查看源码,表示这块比较复杂。 -
xxInLinePatch
这个类是为了处理内联问题而产生的,内联导致了某个方法神奇的“消失”了,我们就把这个消失的代码放到了xxInLinePatch
中。关于内联是什么,请参考Proguard官网。 -
XXPatchRobustAssist
这个类别是为解决super问题而引入的解决办法,具体请参考:Android热更新方案Robust。 -
最后一种是新增类的,
Add
注解加在那个类上,就会把这个类放入补丁内部。
接下来逐个讲解一下各个库
在这个库有几个重要的类:
-
com.meituan.robust.patch.annotaion.Add
这是自动化补丁使用的注解,用来标记新增的类和方法,目前还不支持新增字段。 -
com.meituan.robust.patch.annotaion.Modify
这个注解用来标记被改动的方法或者类,如果这个注解是放在一个类A上面,自动化补丁会生成类APatch,APatch会被打入补丁,原始APK中的类A中每个方法都不会被执行,只会执行APatch中的方法,相当于把A类“替换为”APatch类(请注意这里只是和替换一个类有相同的效果,实际上A类依然在APK中,此时的A成为了一个傀儡,APatch才是幕后黑手);如果注解标记的是方法,则表明这个方法是需要被打入补丁中的,只有被标注的方法会打入补丁,打入补丁之后,就会执行补丁的方法,原始方法不会在执行。 -
com.meituan.robust.patch.RobustModify
上面的Modify
注解并没有完美的标记所有的方法,这是因为在泛型、匿名内部类等问题上,注解会由于泛型的擦除等问题,会产生移动(我称之为注解的漂移),感兴趣的可以自己写一个泛型的方法,使用命令javac -p -v +your.class
查看一下泛型和匿名内部类到底是如何实现的,RobustModify
这个类就是为了解决注解移动的问题,在这个类中有一个方法modify
,针对泛型、匿名内部类等需要在泛型方法里面使用RobustModify.modify();
来标注需要打入补丁中的方法。 -
com.meituan.robust.utils.EnhancedRobustUtils
反射的工具类。 -
com.meituan.robust.utils.PatchTemplate
补丁的模板类,补丁中的类会填充这个模板生成补丁的转发类(这部分可以参看补丁的结构) -
com.meituan.robust.ChangeQuickRedirect
这个接口在上面的代码结构中出现的频率比较高,我们在代码中插入的字段就是这个接口,同时这个接口也是上述xxcontrol
的实现接口,这个接口包含了两个方法:
public interface ChangeQuickRedirect {
Object accessDispatch(String methodName, Object[] paramArrayOfObject);
boolean isSupport(String methodName, Object[] paramArrayOfObject);
}
方法isSupport
是用来判断方法是否需要被替换,而accessDispatch
在自动化补丁中这是对补丁方法的转发。
负责代码插桩:给指定的类插入字段和每个方法前插入代码。其中核心类是robust.gradle.plugin.RobustTransform
,在方法 void transform(...)
中我们提供了两种不同的插桩实现方式,分别为:JavaAssist和ASM两种实现,如果需要个性化定制,请继承InsertcodeStrategy
实现自己插桩逻辑。