美团Robust原理解析

本篇文章将带大家解析Robust框架热修复原理

主流的热修复框架类型

  • ClassLoader:将热修复的类放在dexElements[]的最前面,这样加载类时会优先加载到要修复的类以达到修复目的。如腾讯的Tinker、Nuwa等。
  • Native hook:修改java方法在native层的函数指针,指向修复后的方法以达到修复目的。如阿里的Andifix、DexPosed等。
  • Instant run:在编译打包阶段对每个函数都插入一段控制逻辑代码。就是今天我们要介绍的Robust实现原理。

Robust原理

  • 修复流程
    首先我们先来看下Robust是如何达到修复目的的。

    • 在每个类中注入一个静态变量、在每个方法前插入控制逻辑
    public long getIndex() {
          return 100;
     }
    

    以上代码经过Robust框架注入后会被处理成

    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;
     }
    

    当有补丁的时候changeQuickRedirect的值就不再是空,所以执行到需要热修的方法时就会走到补丁的方法实现而不是原逻辑达到修复目的。

    • 补丁加载
      补丁加载既如何将changeQuickRedirect的值设置成补丁项的


      补丁加载.png

      当应用获取到加载补丁后,会创建DexClassLoader加载补丁,每个补丁有被修复的类信息及该类对应的补丁信息。通过被修复的类信息找到该类,反射将changeQuickRedirect的值赋为补丁对象完成补丁加载操作。

  • 实现原理
    了解修复流程后我们一起看下具体的实现原理,实现原理主要包含三部分:基础包插桩、补丁加载及自动化补丁。
    • 插桩实现: 具体实现在gradle-plugin Moudle中
      原理:在class转dex的过程中会调用Transform,在该时机修改class对象,完成代码的注入。
      • 注册Transfrom
      class RobustTransform extends Transform implements Plugin<Project> {
         @Override
         void apply(Project target) {
          ...
           //解析应用设置的robust.xml文件,确定需要注入类
          robust = new XmlSlurper().parse(new File("${project.projectDir}/${Constants.ROBUST_XML}"))
          ...
         //将该类注入到工程的Transform过程中
          project.android.registerTransform(this)
      }
      
      • 修改class对象:通过 Asm或者JavaAssist操作修改class对象。
        当class转dex时会调用每个Transform类的transform方法。
      @Override
      void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
         ...
        if(useASM){
            insertcodeStrategy=new AsmInsertImpl(hotfixPackageList,hotfixMethodList,exceptPackageList,exceptMethodList,isHotfixMethodLevel,isExceptMethodLevel);
        }else {
            insertcodeStrategy=new JavaAssistInsertImpl(hotfixPackageList,hotfixMethodList,exceptPackageList,exceptMethodList,isHotfixMethodLevel,isExceptMethodLevel);
        }
        insertcodeStrategy.insertCode(box, jarFile);
       ...
      }
      
      接下来看一下JavaAssistInsertImpl注入代码的具体实现
        @Override
       protected void insertCode(List<CtClass> box, File jarFile) throws CannotCompileException, IOException, NotFoundException {
         ...
         for (CtClass ctClass : box) {
            ...
                 boolean addIncrementalChange = false;
                 for (CtBehavior ctBehavior : ctClass.getDeclaredBehaviors()) {
                     if (!addIncrementalChange) {
                         // 1.静态变量注入
                         addIncrementalChange = true;
                         ClassPool classPool = ctBehavior.getDeclaringClass().getClassPool();
                         CtClass type = classPool.getOrNull(Constants.INTERFACE_NAME);
                         CtField ctField = new CtField(type, Constants.INSERT_FIELD_NAME, ctClass);
                         ctField.setModifiers(AccessFlag.PUBLIC | AccessFlag.STATIC);
                         ctClass.addField(ctField);
                     }
                     ....
                     try {
                         if (ctBehavior.getMethodInfo().isMethod()) {
                             CtMethod ctMethod = (CtMethod) ctBehavior;
                             boolean isStatic = (ctMethod.getModifiers() & AccessFlag.STATIC) != 0;
                             CtClass returnType = ctMethod.getReturnType();
                             String returnTypeString = returnType.getName();
                             //2.每个方法前控制逻辑注入
                             String body = "Object argThis = null;";
                             if (!isStatic) {
                                 body += "argThis = $0;";
                             }
                             String parametersClassType = getParametersClassType(ctMethod); 
                             body += "   if (com.meituan.robust.PatchProxy.isSupport($args, argThis, " + Constants.INSERT_FIELD_NAME + ", " + isStatic +
                                     ", " + methodMap.get(ctBehavior.getLongName()) + "," + parametersClassType + "," + returnTypeString + ".class)) {";
                             body += getReturnStatement(returnTypeString, isStatic, methodMap.get(ctBehavior.getLongName()), parametersClassType, returnTypeString + ".class");
                             body += "   }";
                             ctBehavior.insertBefore(body);
                         }
                     } catch (Throwable t) {
                      ...
                     }
                 }
             }
         } 
       }
      

在学习补丁加载流程前,我们先看下每个补丁包的结构

  • 补丁结构:每个补丁包含以下三个部分

    • PatchesInfoImpl:补丁包说明类,可以获取所有补丁对象;每个对象包含被修复类名及该类对应的补丁类。
      public class PatchesInfoImpl implements PatchesInfo
       {
        public List getPatchedClassesInfo()
         {
           ArrayList localArrayList = new ArrayList();
           localArrayList.add(new  PatchedClassInfo("com.xxx.android.robustdemo.MainActivity", "com.bytedance.robust.patch.MainActivityPatchControl"));
           com.meituan.robust.utils.EnhancedRobustUtils.isThrowable = false;
           return localArrayList;
         }
        }
      
      
    • PatchControl:补丁类,具备判断方法是否执行补丁逻辑,及补丁方法的调度。
      public class MainActivityPatchControl implements ChangeQuickRedirect{
        ...
        //1.方法是否支持热修
        public boolean isSupport(String paramString, Object[] paramArrayOfObject)
        {
          Log.d("robust", "arrivied in isSupport " + paramString + " paramArrayOfObject  " + paramArrayOfObject);
          String str = paramString.split(":")[3];
          Log.d("robust", "in isSupport assemble method number  is  " + str);
          Log.d("robust", "arrivied in isSupport " + paramString + " paramArrayOfObject  " + paramArrayOfObject + " isSupport result is " + ":2:".contains(new StringBuffer().append(":").append(str).append(":").toString()));
          return ":2:".contains(":" + str + ":");
        }
        //2.调用补丁的热修逻辑
        public Object accessDispatch(String paramString, Object[] paramArrayOfObject)
        {
          for (;;)
          {
          try
            {
              Object localObject = new java/lang/StringBuffer;
              ((StringBuffer)localObject).<init>();
              if (!paramString.split(":")[2].equals("false")) {
                  continue;
                }
              if (keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]) != null) {
              continue;
            }
            localObject = new com/bytedance/robust/patch/MainActivityPatch;
            ((MainActivityPatch)localObject).<init>(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
            keyToValueRelation.put(paramArrayOfObject[(paramArrayOfObject.length - 1)], null);
            paramArrayOfObject = (Object[])localObject;
            localObject = paramString.split(":")[3];
            paramString = new java/lang/StringBuffer;
            paramString.<init>();
             if (!"2".equals(localObject)) {
                continue;
              }
             paramString = paramArrayOfObject.RobustPublicgetShowText();
           }catch (Throwable paramString){ ...}
           return paramString;
           paramArrayOfObject = (MainActivityPatch)keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]);
           continue;
           //具体实现逻辑在Patch中
           paramArrayOfObject = new MainActivityPatch(null);
         }
        }
       }
      
      • Patch:具体补丁方法的实现。该类中包含被修复类中需要热修的方法。
       public class MainActivityPatch
        {
         MainActivity originClass;
         public MainActivityPatch(Object paramObject)
         {
           this.originClass = ((MainActivity)paramObject);
          }
          //热修的方法具体实现
          private String getShowText()
          {
           Object localObject = getRealParameter(new Object[] { "Error Text" });
           localObject = (String)EnhancedRobustUtils.invokeReflectConstruct("java.lang.String", (Object[])localObject, new Class[] { String.class });
           localObject = getRealParameter(new Object[] { "Fixed Text" });
           return (String)EnhancedRobustUtils.invokeReflectConstruct("java.lang.String", (Object[])localObject, new Class[] { String.class });
           }
         }
      
  • 补丁加载过程:具体实现在patch Moudle中

    • 首先需自定义PatchManipulate实现类,确定如何拉取补丁、校验补丁等逻辑
      public abstract class PatchManipulate {
      /**
       * 获取补丁列表
       *
       * @param context
       * @return 相应的补丁列表
       */
      protected abstract List<Patch> fetchPatchList(Context context);
      
      /**
       * 验证补丁文件md5是否一致
       * 如果不存在,则动态下载
       *
       * @param context
       * @param patch
       * @return 校验结果
       */
      protected abstract boolean verifyPatch(Context context, Patch patch);
      
      /**
       * 努力确保补丁文件存在,验证md5是否一致。
       * 如果不存在,则动态下载
       *
       * @param patch
       * @return 是否存在
       */
      protected abstract boolean ensurePatchExist(Patch patch);
      }
      
    • 开启PatchExecutor,拉取补丁并应用
      PatchExecutor是个线程,该类实现了应用补丁的具体逻辑
      public class PatchExecutor extends Thread {
          ...
          @Override
          public void run() {
                ...
                //拉取补丁列表
                List<Patch> patches = patchManipulate.fetchPatchList(context);
                //应用补丁列表
                applyPatchList(patches);
                 ...
          }
         /**
          * 应用补丁列表
          */
          protected void applyPatchList(List<Patch> patches) {
            ...
            for (Patch p : patches) {
               ...
               if (patchManipulate.ensurePatchExist(p)) {
                   ...
                   patch(context, p);
                   ...
                }
             }
           }
          protected boolean patch(Context context, Patch patch) {
            DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
                  null, PatchExecutor.class.getClassLoader());
            patch.delete(patch.getTempPath());
            Class patchClass, oldClass;
            Class patchsInfoClass;
            PatchesInfo patchesInfo = null;
            try {
              patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
              patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();  
            } catch (Throwable t) {
             ...
            }
            ...
            //1.获取补丁包中所有要热修的对象
            List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
            ...
            for (PatchedClassInfo patchedClassInfo : patchedClasses) {
                String patchedClassName = patchedClassInfo.patchedClassName;
                String patchClassName = patchedClassInfo.patchClassName;
                ...
                try {
                  //2.加载被修复的类
                  oldClass = classLoader.loadClass(patchedClassName.trim());
                  Field[] fields = oldClass.getDeclaredFields(); 
                  Field changeQuickRedirectField = null;
                  for (Field field : fields) {
                      if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
                          //3.找到注入的静态变量
                          changeQuickRedirectField = field;
                          break;
                      }
                  }
                  ...
                  try {
                      //4.加载对应的补丁类
                      patchClass = classLoader.loadClass(patchClassName);
                      Object patchObject = patchClass.newInstance();
                      changeQuickRedirectField.setAccessible(true);
                      //5.将静态变量值设置为补丁
                      changeQuickRedirectField.set(null, patchObject);
                  } catch (Throwable t) {
                    ...
                  }
                } catch (Throwable t) {
                   ...
                }
            }
            return true;
          }
       }
      
  • 自动化补丁

    • Robust支持补丁自动化生成,具体操作如下。
      • 在修复完的方法上添加@Modify注解或者 “RobustModify.modify();”,新创建的方法或类添加@Add注解。
      • 工程添加依赖 apply plugin: 'auto-patch-plugin',编译完成后会在outputs/robust目录下生成patch.jar。
    • 自动化补丁生成原理简析
      自动化补丁具体实现在auto-patch-plugin Moudle中,也是利用了Transform API自动生成jar。本篇文章我们只关注如何自动生成patch类。
      private CtClass createPatchClass(CtClass modifiedClass, boolean isInline, String patchName, Set patchMethodSignureSet, String patchPath) throws CannotCompileException, IOException, NotFoundException {
        ...
        //1.将包含修复后逻辑的类clone一份
        CtClass temPatchClass = cloneClass(modifiedClass, patchName, methodNoNeedPatchList);
        ...
        for (CtMethod method : temPatchClass.getDeclaredMethods()) {
         
            if (!Config.addedSuperMethodList.contains(method) && reaLParameterMethod != method && !method.getName().startsWith(Constants.ROBUST_PUBLIC_SUFFIX)) {
                //2.将字段、方法调用通过反射实现
                method.instrument(
                        new ExprEditor() {
                            public void edit(FieldAccess f) throws CannotCompileException {
                                if (Config.newlyAddedClassNameList.contains(f.getClassName())) {
                                    return;
                                }
                                Map memberMappingInfo = getClassMappingInfo(f.getField().declaringClass.name);
                                try {
                                    if (f.isReader()) {
                                        f.replace(ReflectUtils.getFieldString(f.getField(), memberMappingInfo, temPatchClass.getName(), modifiedClass.getName()));
                                    } else if (f.isWriter()) {
                                        f.replace(ReflectUtils.setFieldString(f.getField(), memberMappingInfo, temPatchClass.getName(), modifiedClass.getName()));
                                    }
                                } catch (NotFoundException e) {
                                    e.printStackTrace();
                                    throw new RuntimeException(e.getMessage());
                                }
                            }
      
      
                            @Override
                            void edit(NewExpr e) throws CannotCompileException {
                                //inner class in the patched class ,not all inner class
                                if (Config.newlyAddedClassNameList.contains(e.getClassName()) || Config.noNeedReflectClassSet.contains(e.getClassName())) {
                                    return;
                                }
      
                                try {
                                    if (!ReflectUtils.isStatic(Config.classPool.get(e.getClassName()).getModifiers()) && JavaUtils.isInnerClassInModifiedClass(e.getClassName(), modifiedClass)) {
                                        e.replace(ReflectUtils.getNewInnerClassString(e.getSignature(), temPatchClass.getName(), ReflectUtils.isStatic(Config.classPool.get(e.getClassName()).getModifiers()), getClassValue(e.getClassName())));
                                        return;
                                    }
                                } catch (NotFoundException e1) {
                                    e1.printStackTrace();
                                }
      
                                e.replace(ReflectUtils.getCreateClassString(e, getClassValue(e.getClassName()), temPatchClass.getName(), ReflectUtils.isStatic(method.getModifiers())));
                            }
      
                            @Override
                            void edit(Cast c) throws CannotCompileException {
                                MethodInfo thisMethod = ReflectUtils.readField(c, "thisMethod");
                                CtClass thisClass = ReflectUtils.readField(c, "thisClass");
      
                                def isStatic = ReflectUtils.isStatic(thisMethod.getAccessFlags());
                                if (!isStatic) {
                                    //inner class in the patched class ,not all inner class
                                    if (Config.newlyAddedClassNameList.contains(thisClass.getName()) || Config.noNeedReflectClassSet.contains(thisClass.getName())) {
                                        return;
                                    }
                                    // static函数是没有this指令的,直接会报错。
                                    c.replace(ReflectUtils.getCastString(c, temPatchClass))
                                }
                            }
      
                            @Override
                            void edit(MethodCall m) throws CannotCompileException {
      
                                //methods no need reflect
                                if (Config.noNeedReflectClassSet.contains(m.method.declaringClass.name)) {
                                    return;
                                }
                                if (m.getMethodName().contains("lambdaFactory")) {
                                    //method contain modifeid class
                                    m.replace(ReflectUtils.getNewInnerClassString(m.getSignature(), temPatchClass.getName(), ReflectUtils.isStatic(method.getModifiers()), getClassValue(m.getClassName())));
                                    return;
                                }
                                try {
                                    if (!repalceInlineMethod(m, method, false)) {
                                        Map memberMappingInfo = getClassMappingInfo(m.getMethod().getDeclaringClass().getName());
                                        if (invokeSuperMethodList.contains(m.getMethod())) {
                                            int index = invokeSuperMethodList.indexOf(m.getMethod());
                                            CtMethod superMethod = invokeSuperMethodList.get(index);
                                            if (superMethod.getLongName() != null && superMethod.getLongName() == m.getMethod().getLongName()) {
                                                String firstVariable = "";
                                                if (ReflectUtils.isStatic(method.getModifiers())) {
                                                    //修复static 方法中含有super的问题,比如Aspectj处理后的方法
                                                    MethodInfo methodInfo = method.getMethodInfo();
                                                    LocalVariableAttribute table = methodInfo.getCodeAttribute().getAttribute(LocalVariableAttribute.tag);
                                                    int numberOfLocalVariables = table.tableLength();
                                                    if (numberOfLocalVariables > 0) {
                                                        int frameWithNameAtConstantPool = table.nameIndex(0);
                                                        firstVariable = methodInfo.getConstPool().getUtf8Info(frameWithNameAtConstantPool)
                                                    }
                                                }
                                                m.replace(ReflectUtils.invokeSuperString(m, firstVariable));
                                                return;
                                            }
                                        }
                                        m.replace(ReflectUtils.getMethodCallString(m, memberMappingInfo, temPatchClass, ReflectUtils.isStatic(method.getModifiers()), isInline));
                                    }
                                } catch (NotFoundException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
            }
        }
        ...
        return patchClass;
      }
      
      

    简单说就是首先将包含修复完代码的类复制一份,然后将方法里的字段、方法调用都使用反射方式调用。为什么用反射呢?因为patch里只包含要修复的代码,调用非修复方法内的代码正常是调用不到的,所以使用反射。

Robust优缺点分析

  • 优点
    • 兼容性好:Robust没有hook Android系统源码,也不需要对JVM做版本适配还借鉴了Google推出的Instant Run原理,所以兼容性是真真儿的好。
    • 实时生效
  • 缺点
    • 增加包体积
    • 不支持so文件和资源替换:美团官网说已经在内侧中

以上就是本文全部内容,欢迎所有善意的交流和指正!_

参考文献:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容