Skip to content

Robust代码结构

何定旭 edited this page Nov 24, 2017 · 7 revisions

本文对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.SampleClassPatchControlxxPatchControl类被我们称为转发器,负责把方法转发到对应的补丁方法。

  • xxPatchControlPatchControl结尾的类被我们成为转发器,这个类的主要职责就是把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注解加在那个类上,就会把这个类放入补丁内部。

接下来逐个讲解一下各个库

autopatchbase

在这个库有几个重要的类:

  • 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 在自动化补丁中这是对补丁方法的转发。

gradle-plugin

负责代码插桩:给指定的类插入字段和每个方法前插入代码。其中核心类是robust.gradle.plugin.RobustTransform,在方法 void transform(...)中我们提供了两种不同的插桩实现方式,分别为:JavaAssist和ASM两种实现,如果需要个性化定制,请继承InsertcodeStrategy实现自己插桩逻辑。