美团Robust原理解析

2,988 阅读7分钟
原文链接: www.jianshu.com

本篇文章将带大家解析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文件和资源替换:美团官网说已经在内侧中

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

参考文献: