热更新框架

  • Robust是基于方法插桩的热修复,对系统侵入性最小(兼容性最高),可以及时生效,不存在混合编译的问题
  • Nuwa是基于Dex顺序的热修复,需要插桩来解决PreVerify问题,但会增大冷启动时间,需要hook系统方法,需要重启生效,存在混合编译的问题
  • Tinker是基于Dex的热修复,流程复杂,需要hook系统方法,需要重启生效,存在混合编译问题
  • AndFix等native层热修复效率高、及时生效,但系统兼容性差,修复率低

Nuwa:通过makeDexElement构造Dex并插入DexPathList最前面、通过Javassist插桩调用patch.dex来绕过preverify机制、通过开源工具jclasslib来比对class文件差异

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, 
                                   String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
}

final class DexPathList {
    private final Element[] dexElements;
    public DexPathList(ClassLoader definingContext, String dexPath,
                               String libraryPath, File optimizedDirectory) {
        this.definingContext = definingContext;
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }
    public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        return null;
    }
}

Robust主要有三个流程:代码插桩、生成补丁、加载补丁,下面分别介绍各个流程:

代码插桩:在使用ASM插桩时,我们向每个类中插入一个静态PatchControl变量,其中,PatchControl实现了两个方法(isNeedPatch和accessPatch),同时我们在每个方法最前面插入一个PatchCheckHelper#check方法调用,由该方法来判断是否需要加载Patch,首先检查PatchControl是否为空,如果不为空,调用PatchControl#isNeedPatch方法,如果该方法返回true,会接着执行PatchControl#accessPatch方法,从而绕过原有逻辑

public class PatchCheckHelper {
  public static PatchCheckResult check(Object[] paramsArray,
                                       Object current, PatchControl patchControl, int methodNumber) {
    PatchCheckResult patchCheckResult = new PatchCheckResult();
    if (isNeedPatch(methodNumber)) {
      patchCheckResult.needPatch = true;
      patchCheckResult.result = getResult(paramsArray, current, patchControl, methodNumber);
    }
    return patchCheckResult;
  }
  private static boolean isNeedPatch(int methodNumber) {
    return incementalChange != null && incrementalChange.isNeedPatch(methodNumber);
  }
  private static Object getResult(Object[] paramsArray, Object current,
                            PatchControl patchControl, int methodNumber) {
    return patchControl.accessPatch(current, methodNumber, objects);
  }
}

首先创建名为patch的Extension,用于设置配置信息

patch {
    applicationId = "com.netease.lede.androidfix"
    patchId = android.defaultConfig.versionName
    patchPackage = 'com/lede/patchbuild/patchinfo'
    notInsertPackage = ["com.netease.ntespmmvp"]
    ifSmall = false
}

在插件中,我们判断当前任务的buildType,如果包含"PATCH"或"DEBUG"字段则不进行插桩操作

def extensions = project.extensions.findByName('patch')
if (extensions == null) {
  extensions = project.extensions.create('patch', PatchExtension)
}
String taskNames = project.gradle.startParameter.taskNames.toString()
if (taskNames.toUpperCase().contains("PATCH") || taskNames.toUpperCase().contains("DEBUG")) {
  return
}

通过Transform Api,我们拿到所有类文件,在插桩过程中,我们仅仅重命名Jar文件,因为Jar文件一般是第三方依赖库

transformInvocation.inputs.each { TransformInput input ->
  input.jarInputs.each { JarInput jarInput ->
    //重命名输出文件(同目录copyFile会冲突)
    def jarName = jarInput.name
    def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
    if (jarName.endsWith(".jar")) {
       jarName = jarName.substring(0, jarName.length() - 4)
    }
    def dest = transformInvocation.outputProvider.getContentLocation(
                    jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
    FileUtils.copyFile(jarInput.file, dest)
    jarList.add(dest.getAbsolutePath())
  }
  input.directoryInputs.each { DirectoryInput directoryInput ->
    //文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等
    listJarFiles(exploded_aar)
    injectDir(directoryInput.file.absolutePath, androidJarPath,
                                    apacheHttp, jarList, patchPackage, notInsertPackage)
    def dest = transformInvocation.outputProvider.getContentLocation(directoryInput.name, 
                           directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
    FileUtils.copyDirectory(directoryInput.file, dest)
  }
}

收集所有的类到ClassPool中,包括android.jar、apache.jar和各种第三方jar包,在插桩过程中,首先判断要插桩的类是否在当前Package中,然后还要排除R类、BuildConfig类、接口类和枚举类

dir.eachFileRecurse { File file ->
  String filePath = file.absolutePath
  //确保当前文件是class文件,并且不是系统自动生成的class文件,排除匿名内部类
  if (filePath.endsWith(".class") &&
                    !filePath.contains("R.class") && !filePath.contains("BuildConfig.class")) {
  // 判断当前目录是否是在我们的应用包里面
  if (filePath.indexOf(patchPackage) == -1) {
    String classNameTemp = filePath.replace(path, "").replace("\\", ".").replace("/", ".")
    String className = classNameTemp.substring(1, classNameTemp.length() - 6)
    //开始修改class文件
    CtClass c = pool.getCtClass(className)
    if (c.isFrozen()) c.defrost();
    if (!c.isInterface() && !c.isEnum()) {
      c.setModifiers(AccessFlag.setPublic(c.getModifiers())) //类改为public
      AsmInject asmInject = new AsmInject()
      byte[] code = asmInject.transformCode(c.toBytecode(), c.getName().replaceAll("\\.", "/"))
      FileOutputStream fos = new FileOutputStream(filePath)
      fos.write(code)
      fos.close()
    }
    c.detach()
  }
}

插桩时要注意方法数超过65535的问题,实现上我们跳过了被ProGuard内联可能性较大的函数(函数内部没有方法调用)

public byte[] transformCode(byte[] b1, String className) throws IOException {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    ClassReader cr = new ClassReader(c.toBytecode());
    ClassNode classNode = new ClassNode();
    Map<String, Boolean> methodInstructionTypeMap = new HashMap<>();
    cr.accept(classNode, 0);
    final List<MethodNode> methods = classNode.methods;
    for (MethodNode m : methods) {
        InsnList inList = m.instructions;
        boolean isMethodInvoke = false;
        for (int i = 0; i < inList.size(); i++) {
            if (inList.get(i).getType() == AbstractInsnNode.METHOD_INSN) {
                isMethodInvoke = true;
            }
        }
        methodInstructionTypeMap.put(m.name + m.desc, isMethodInvoke);
    }
    InsertMethodBodyAdapter insertMethodBodyAdapter = 
                 new InsertMethodBodyAdapter(cw, className, methodInstructionTypeMap);
    cr.accept(insertMethodBodyAdapter, ClassReader.EXPAND_FRAMES);
    return cw.toByteArray();
}

不需要插桩的函数:构造函数(大多数都是默认构造函数)、抽象方法、接口方法、本地方法、过时方法、容易内联的函数

通过方法签名(visitMethod参数中包含name和desc)生成MD5值来标记方法。将已插桩的方法存入methods.map文件,便于生成Patch时验证

class InsertMethodBodyAdapter extends ClassVisitor implements Opcodes {
  public InsertMethodBodyAdapter(ClassWriter cw, String className,
                                  Map<String, Boolean> methodInstructionTypeMap) {
      super(Opcodes.ASM5, cw);
      this.classWriter = cw;
      this.className = className;
      this.methodInstructionTypeMap = methodInstructionTypeMap;
      classWriter.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$ledeIncementalChange",
                    Type.getDescriptor(LedeIncementalChange.class), null, null);
  }
  @Override
  public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
    if (isProtect(access)) {
      access = setPublic(access);
    }
    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
    if (!isQualifiedMethod(access, name, desc, methodInstructionTypeMap)) return mv;
    int methodId = (name + desc).hashCode();//存储methodMap
    return new MethodBodyInsertor(mv, className, desc,
                                isStatic(access), String.valueOf(methodId), name, access);
  }

  private int setPublic(int access) {
    return (access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC;
  }

  //是否应该插桩
  private boolean isQualifiedMethod(int access, String name, String desc, Map<String, Boolean> c) {
    if ("<init>".equals(name) || "<clinit>".equals(name)) return false;
    if ((access & Opcodes.ACC_ABSTRACT) != 0) return false;
    if ((access & Opcodes.ACC_NATIVE) != 0) return false;
    if ((access & Opcodes.ACC_INTERFACE) != 0) return false;
    if ((access & Opcodes.ACC_DEPRECATED) != 0) return false;
    return c.getOrDefault(name + desc, false);//是否存在函数调用
  }
}

通过MethodVisitor的visitCode入口来处理函数逻辑

class MethodBodyInsertor extends GeneratorAdapter implements Opcodes {
    String className;
    Type[] argsType;
    Type returnType;
    List<Type> paramsTypeClass = new ArrayList<>();
    boolean isStatic;
    String methodId;
    public MethodBodyInsertor(MethodVisitor mv, String className, String desc,
                                      boolean isStatic, String methodId, String name, int access) {
        super(Opcodes.ASM5, mv, access, name, desc);
        this.className = className;
        this.returnType = Type.getReturnType(desc);
        Type[] argsType = Type.getArgumentTypes(desc);
        for (Type type : argsType) {
            paramsTypeClass.add(type);
        }
        this.isStatic = isStatic;
        this.methodId = methodId;
    }
    @Override
    public void visitCode() {
        super.visitCode();
        AsmInsertUtils.createInsertCode(this, className, paramsTypeClass,
                                              returnType, isStatic, Integer.valueOf(methodId));
    }
}

下面是真正生成代码的部分,涉及到很多JVM指令集

static void createInsertCode(GeneratorAdapter mv, String className, List<Type> args,
                                         Type returnType, boolean isStatic, int methodId) {
    prepareMethodParameters(mv, className, args, returnType, isStatic, methodId);
    mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                      "com.lede.ledefix.common.PatchCheckHelper".replace(".", "/"),
                      "check",
                      "([Ljava/lang/Object;Ljava/lang/Object;" + 
                       Type.getDescriptor(LedeIncementalChange.class) + 
                      "I)Lcom/lede/ledefix/common/PatchCheckResult;",false);
    int local = mv.newLocal(Type.getType("Lcom/lede/common/PatchCheckResult;"));
    mv.storeLocal(local);
    mv.loadLocal(local);
    mv.visitFieldInsn(Opcodes.GETFIELD,
                     "com/lede/ledefix/common/PatchCheckResult", "needPatch", "Z");
    Label l1 = new Label();
    mv.visitJumpInsn(Opcodes.IFEQ, l1);
    //判断是否有返回值,代码不同
    if ("V".equals(returnType.getDescriptor())) {
        mv.visitInsn(Opcodes.RETURN);
    } else {
        mv.loadLocal(local);
        mv.visitFieldInsn(Opcodes.GETFIELD, "com/lede/ledefix/common/PatchCheckResult",
                                    "result", "Ljava/lang/Object;");
        //强制转化类型
        if (!castPrimateToObj(mv, returnType.getDescriptor())) {
            //这里需要注意,如果是数组类型的直接使用即可,如果非数组类型,就得去除前缀了,还有最终是没有结束符;
            //比如:Ljava/lang/String; ==》 java/lang/String
            String newTypeStr = null;
            int len = returnType.getDescriptor().length();
            if (returnType.getDescriptor().startsWith("[")) {
                newTypeStr = returnType.getDescriptor().substring(0, len);
            } else {
                newTypeStr = returnType.getDescriptor().substring(1, len - 1);
            }
            mv.visitTypeInsn(Opcodes.CHECKCAST, newTypeStr);
        }
        mv.visitInsn(getReturnTypeCode(returnType.getDescriptor()));//返回类型不同返回指令也不同
    }
    mv.visitLabel(l1);
}

private static void prepareMethodParameters(GeneratorAdapter mv, String className,
                            List<Type> args, Type returnType, boolean isStatic, int methodId) {
    //第一个参数:new Object[]{...};,如果方法没有参数直接传入new Object[0]
    if (args.size() == 0) {
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
    } else {
        createObjectArray(mv, args, isStatic);
    }
    //第二个参数:this,如果方法是static的话就直接传入null
    if (isStatic) {
        mv.visitInsn(Opcodes.ACONST_NULL);
    } else {
        mv.visitVarInsn(Opcodes.ALOAD, 0);
    }
    //第三个参数:PatchControl实例
    mv.visitFieldInsn(Opcodes.GETSTATIC, className,
              "$ledeIncementalChange", Type.getDescriptor(LedeIncementalChange.class));
    //第四个参数:方法的Md5值
    mv.push(methodId);
}

private static void createObjectArray(MethodVisitor mv,
                          List<Type> paramsTypeClass, boolean isStatic) {
    int argsCount = paramsTypeClass.size();
    if (argsCount >= 6) {
        mv.visitIntInsn(Opcodes.BIPUSH, argsCount);
    } else {
        mv.visitInsn(Opcodes.ICONST_0 + argsCount);
    }
    mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
    int loadIndex = (isStatic ? 0 : 1);
    for (int i = 0; i < argsCount; i++) {
        mv.visitInsn(Opcodes.DUP);
        if (i <= 5) {
            mv.visitInsn(Opcodes.ICONST_0 + i);
        } else {
            mv.visitIntInsn(Opcodes.BIPUSH, i);
        }
        //这里又要做特殊处理,当一个参数的前面一个参数是long或者是double类型的话
        //后面参数在使用LOAD指令,加载数据索引值要+1
        if (i >= 1) {
            if ("J".equals(paramsTypeClass.get(i - 1).getDescriptor()) ||
                "D".equals(paramsTypeClass.get(i - 1).getDescriptor())) {
                loadIndex++;
            }
        }
        if (!createPrimateTypeObj(mv, loadIndex,
                               paramsTypeClass.get(i).getDescriptor())) {
            mv.visitVarInsn(Opcodes.ALOAD, loadIndex);
            mv.visitInsn(Opcodes.AASTORE);
        }
        loadIndex++;
    }
}

private static int getReturnTypeCode(String typeS) {
    if ("Z".equals(typeS)) {
        return Opcodes.IRETURN;
    }
    if ("B".equals(typeS)) {
        return Opcodes.IRETURN;
    }
    if ("C".equals(typeS)) {
        return Opcodes.IRETURN;
    }
    if ("S".equals(typeS)) {
        return Opcodes.IRETURN;
    }
    if ("I".equals(typeS)) {
        return Opcodes.IRETURN;
    }
    if ("F".equals(typeS)) {
        return Opcodes.FRETURN;
    }
    if ("D".equals(typeS)) {
        return Opcodes.DRETURN;
    }
    if ("J".equals(typeS)) {
        return Opcodes.LRETURN;
    }
    return Opcodes.ARETURN;
}

补丁运行原理:在Patch中主要有五个类,分别是PatchLoader、PatchControl、Patch、Inline、Assist,其中PatchLoader指定补丁中所有PatchControl类的全路径,PatchControl通过注解标明要修复的类,并实现了isNeedPatch和accessPatch方法,在PatchControl#isNeedPatch中,我们通过方法数(通过方法的descriptor和name拼接的MD5生成)来唯一表示一个方法,最终调用Patch#accessPatch方法来调用修复后的逻辑

public class AppPatchLoaderImpl extends AbstractPatchLoader {
  public String[] getPatchedClasses() {
    return new String[]{"com.lede.patchbuild.patchinfo.MainActivityPatchControl"};
  }
}

@ClassReplace(toFixClass = MainActivity.class)
public class MainActivityPatchControl extends LedeIncementalChange {
  public Object accessPatch(Object host, int methodSignatureHashCode, Object[] params) {
    MainActivityPatch var4 = null;
    var4 = new MainActivityPatch(host);
    if(methodSignatureHashCode == 1496845498) {
      var4.onCreate((Bundle)params[0]);
    }
    if(methodSignatureHashCode == -1780868813) {
      var4.loadPatch();
    }
    return null;
  }
  public boolean isNeedPatch(int var1) {
    return var1 == 1496845498 ? true : var1 == -1780868813;
  }
}

public class MainActivityPatch {

  static MainActivity originClass;

  protected void onCreate(Bundle savedInstanceState) {
    staticLedeonCreate(this, originClass, savedInstanceState);
    int var3 = 2130968603;
    Object var5;
    //不用想的很复杂,每个非静态的methodCall都会有$0,如果$0在原方法(onCreate)
    //中代表this,由于this肯定等于this(字符串比较),这时替换成originClass
    //stringBuilder.append(" if(\$0 == this ){")
    //stringBuilder.append("instance = ((" + patchClass.getName() + ")\$0).originClass;")
    //stringBuilder.append("}else{")
    //stringBuilder.append("instance=\$0;")
    //stringBuilder.append("}")
    if(this == this) {
      var5 = originClass;
    } else {
      var5 = this;
    }
    Object[] var6 = this.getRealParameter(new Object[]{new Integer(var3)});
    EnhancedReflectUtils.invokeReflectMethod(originClass, "setContentView",
                           var5, var6, new Class[]{Integer.TYPE}, AppCompatActivity.class);
  }

  public static void staticLedeonCreate(MainActivityPatch var0, MainActivity var1, Bundle var2) {
    MainActivityPatchLedeAssist.staticLedeonCreate(var0, var1, var2);
  }

  private void loadPatch() {
    Application var1 = null;
    LocalPatchLoader var2 = null;
    Object[] var4 = this.getRealParameter(new Object[0]);
    var2 = (LocalPatchLoader)EnhancedReflectUtils.invokeReflectStaticMethod(originClass,
                                              "a", LocalPatchLoader.class, var4, (Class[])null);
    LocalPatchLoader var10000 = var2;
    var2 = null;
    Object var3;
    MainActivityPatch var10001;
    if(this == this) {
      var10001 = (MainActivityPatch)this;
      var3 = originClass;
    } else {
      var3 = this;
    }
    Application var10 = (Application)EnhancedReflectUtils.invokeReflectMethod(originClass, 
                      "getApplication", var3, new Object[0], (Class[])null, Activity.class);
    var1 = var10;
    var2 = null;
    if(var1 == this) {
      var10001 = (MainActivityPatch)var1;
      var3 = originClass;
    } else {
      var3 = var1;
    }
    ClassLoader var11 = (ClassLoader)EnhancedReflectUtils.invokeReflectMethod(originClass, 
                     "getClassLoader", var3, new Object[0], (Class[])null, ContextWrapper.class);
    ClassLoader var16 = var11;
    var2 = null;
    MainActivityPatch var10002;
    if(this == this) {
      var10002 = (MainActivityPatch)this;
      var3 = originClass;
    } else {
      var3 = this;
    }
    var10 = (Application)EnhancedReflectUtils.invokeReflectMethod(originClass, 
                          "getApplication", var3, new Object[0], (Class[])null, Activity.class);
    var1 = var10;
    var2 = null;
    if(var1 == this) {
      var10002 = (MainActivityPatch)var1;
      var3 = originClass;
    } else {
      var3 = var1;
    }
    String var13 = (String)EnhancedReflectUtils.invokeReflectMethod(originClass, 
                      "getPackageName", var3, new Object[0], (Class[])null, ContextWrapper.class);
    String var5 = "com.lede.patchbuild.patchinfo";
    String var12 = "1.2.0";
    String var14 = var13;
    var11 = var16;
    LocalPatchLoader var9 = var10000;
    Object var7;
    if(var9 == this) {
      MainActivityPatch var15 = (MainActivityPatch)var9;
      var7 = originClass;
    } else {
      var7 = var9;
    }
    Object[] var8 = this.getRealParameter(new Object[]{var11, var14, var12, var5});
    EnhancedReflectUtils.invokeReflectMethod(originClass, "a", var7, var8, 
                    new Class[]{ClassLoader.class, String.class, String.class, String.class},
                    LocalPatchLoader.class);
  }

  public Object[] getRealParameter(Object[] var1) {
    if(var1 != null && var1.length >= 1) {
      Object[] var2 = new Object[var1.length];
      for(int var3 = 0; var3 < var1.length; ++var3) {
        if(var1[var3] instanceof Object[]) {
          var2[var3] = this.getRealParameter((Object[])var1[var3]);
        } else if(var1[var3] == this) {
          var2[var3] = originClass;
        } else {
          var2[var3] = var1[var3];
        }
      }
      return var2;
    } else {
      return var1;
    }
  }
}

public class MainActivityPatchLedeAssist extends AppCompatActivity {
  public static void staticLedeonCreate(MainActivityPatch var0, MainActivity var1, Bundle var2) {
    var0.onCreate(var2);
  }
  public MainActivityPatchLedeAssist() {
  }
}

生成补丁:生成Patch的流程较为复杂,需要考虑的问题有:super调用、内联、泛型方法、this对象替换

使用@Modify标记修改的方法和类(意味着全部方法都被修改),使用@Add标记新生成的类,不允许在已有的类中新增方法,对于泛型方法,编译中会增加一个桥方法,实际方法的调用在桥方法中进行,对于lamda表达式也是如此,这种情况下我们需要增加一个静态方法LedeFix.modify()来标注修改的地方。将线上包对应的mapping.txt、R.java、methods.map拷贝到release文件夹下,运行assembleNormalPatch生成补丁

public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;
    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }
    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

Demo代码:

public class MainActivity extends AppCompatActivity {
  @Modify
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  @Modify
  private void loadPatch() {
    LocalPatchLoader.getInstance().loadPatch(getApplication().getClassLoader(),
                    getApplication().getPackageName(), "1.2.0", "com.lede.patchbuild.patchinfo");
  }

  //该方法从Mapping文件中删除,模拟内联情况
  public void init() {

  }
}

@Add
public class NewClass {
  public static int addFuction(String s) {
    new MainActivity().init();
    return 1;
  }
}

在AutoPatchPlugin中,如果不是打补丁任务,则不执行该插件

String taskNames = project.gradle.startParameter.taskNames.toString()
if (taskNames.toUpperCase().contains("PATCH")) {
    project.android.registerTransform(new AutoPatchTransform(project))
} else {
    return
}

由于补丁代码中可能包含资源信息,所以需要hook aapt替换新生成的R文件

ProcessAndroidResources aapt = project.tasks["process${variantName}Resources"]
File sourceDir = aapt.sourceOutputDir
def rJavaFile = new File(sourceDir, "${packagePath}/R.java")
hookAapt(aapt, rJavaFile)

注册Transform Api,收集所有的CtClass,扫描注解文件,找到修改的方法和新添加的类,通过methods.map判断待修复方法是否在正式包中

static void readAnnotation(List<CtClass> box) {
    synchronized (AutoPatchTransform.class) {
        if (ModifyAnnotationClass == null) {
            ModifyAnnotationClass = box.get(0).getClassPool().get(MODIFY_ANNOTATION).toClass()
        }
        if (AddAnnotationClass == null) {
            AddAnnotationClass = box.get(0).getClassPool().get(ADD_ANNOTATION).toClass()
        }
    }
    box.forEach {
        ctClass ->
            try {
                boolean isNewlyAddClass = scanClassForAddClassAnnotation(ctClass)
                //非新添加的类才需要扫描是否有新增或修改的方法
                if (!isNewlyAddClass) {
                    Global.patchMethodSignatureSet.addAll(scanClassForModifyMethod(ctClass))
                }
            } catch (Exception e) {
                e.printStackTrace()
            }
    }
    //com.netease.lede.sample.NewClass
    Utils.printList(Global.newlyAddedClassNameList)
    //com.netease.lede.sample.MainActivity
    Utils.printList(Global.modifiedClassNameList)
    // com.netease.lede.sample.MainActivity.loadPatch()
    // com.netease.lede.sample.MainActivity.onCreate(android.os.Bundle)
    Utils.printList(Global.patchMethodSignatureSet.asList())
}

static boolean scanClassForAddClassAnnotation(CtClass ctClass) {
    Add addClassAnnotation = ctClass.getAnnotation(AddAnnotationClass) as Add
    if (addClassAnnotation != null && !Global.newlyAddedClassNameList.contains(ctClass.name)) {
        Global.newlyAddedClassNameList.add(ctClass.name)
        return true
    }
    return false
}

static Set scanClassForModifyMethod(CtClass ctClass) {
    Set patchMethodSignatureSet = new HashSet<String>()
    boolean isAllMethodsPatch = true
    ctClass.declaredMethods.findAll {
        return it.hasAnnotation(ModifyAnnotationClass)
    }.each {
        method ->
            isAllMethodsPatch = false
            addPatchMethodAndModifiedClass(patchMethodSignatureSet, method)
    }
    //处理使用LedeFixModify.modify标注的情况
    ctClass.defrost()
    ctClass.declaredMethods.each {
        method ->
            method.instrument(new ExprEditor() {
                @Override
                void edit(MethodCall m) throws CannotCompileException {
                    try {
                        if (LAMBDA_MODIFY.equals(m.method.declaringClass.name)) {
                            isAllMethodsPatch = false
                            addPatchMethodAndModifiedClass(patchMethodSignatureSet, method)
                        }
                    } catch (Exception ignore) {
                            ignore.printStackTrace()
                    }
                }
            })
    }
    return patchMethodSignatureSet
}

static void addPatchMethodAndModifiedClass(Set patchMethodSignatureSet, CtMethod method) {
    patchMethodSignatureSet.add(method.longName)
    if (!Global.modifiedClassNameList.contains(method.declaringClass.name)) {
        Global.modifiedClassNameList.add(method.declaringClass.name)
    }
}

扫描补丁中用到的内联方法(根据入口(新添加类的所有方法、修改的方法)逐一扫描,如果两次扫描没有新增的内联方法,则认为找到了所有),生成对应的InLine类,并修改新增方法对内联方法的调用,其他方法在生成Patch和Inline时会处理对内联方法的调用,至于Assist类不用考虑内联情况(父类方法未修改)

/*如何识别内联方法*/
modifiedCtClass = Global.classPool.get(fullClassName) //com.netease.lede.sample.NewClass
modifiedCtClass.declaredMethods.each {
    method ->
        //如果是新增加的类,或者方法签名集合中有这个方法
        if (isNewClass || allPatchMethodSignatureSet.contains(method.longName)) {
            method.instrument(new ExprEditor() {
                @Override
                void edit(MethodCall m) throws CannotCompileException {
                    Map<String, String> classMapping = MappingManager.getInstance().
                                              getClassMapping(m.method.declaringClass.name)
                    //查找mapping文件中有没有这个方法的混淆记录,如果没有则说明是内联函数
                    if (null != classMapping && 
      classMapping.get(ReflectUtils.getJavaMethodSignatureWithReturnType(m.method)) == null) {
                                    
                    }
                }
            })
        }
}
/**
 * 如何创建InLine类
 * patchPath:/Users/wangzihan/Project/lede-fix/sample-android/build/outputs/ledefix/
 * patchName:com.lede.patchbuild.patchinfo.MainActivityInLinePatch
 * patchMethodSignatureSet:com.netease.lede.sample.MainActivity.init()
 */
for (String fullClassName : classInLineMethodsMap.keySet()) {
    CtClass inlineClass = Global.classPool.get(fullClassName)
    List<String> inlineMethod = 
             classInLineMethodsMap.getOrDefault(fullClassName, new ArrayList<String>())
    if(inlineMethod == null || inlineMethod.isEmpty()) continue
    CtClass inlinePatchClass = 
               PatchesFactory.createPatchClass(patchPath, true, inlineClass,
               NameManger.getInstance().getInlinePatchName(inlineClass.name),
               inlineMethod.toSet())
    inlinePatchClass.writeFile(patchPath)
}

接着生成Patch类,在生成Patch类时要生成对应的Assist类(super调用),并修改Patch类对内联方法的调用

private static CtClass createPatchClass(String patchPath, final boolean isInline, 
                final CtClass modifiedClass, String patchName, Set patchMethodSignatureSet) {
    for (CtMethod method : modifiedClass.getDeclaredMethods()) {
        if (!patchMethodSignatureSet.contains(method.getLongName())) {
            exceptMethodList.add(method);
        }
    }
    //复制要修改的函数和所有的变量
    final CtClass temPatchClass = cloneClass(modifiedClass, patchName, exceptMethodList);
    //添加传入原始参数的构造器
    JavaUtils.addPatchConstruct(temPatchClass, modifiedClass);
    //添加获取真正参数的方法,其实只是把this替换成原始的class
    CtMethod reaLParameterMethod =
                   CtMethod.make(JavaUtils.getRealParametersBody(), temPatchClass);
    temPatchClass.addMethod(reaLParameterMethod);
    //处理super问题,生成staticLede方法,生成对应的Assist类。
    //在处理Inline的时候这步是直接跳过的,生成Patch文件的时候才会用到
    dealWithSuperMethod(temPatchClass, modifiedClass, patchPath);
    //后面通过method#instrument来遍历所有方法,替换super调用,替换inline调用,替换成反射逻辑
    for (final CtMethod method : temPatchClass.getDeclaredMethods()) {
        if (!reaLParameterMethod.equals(method) && 
                       !method.getName().startsWith(Constants.LEDE_PUBLIC_SUFFIX)) {
            method.instrument(new ExprEditor() {

            });
        }
    }
}

public static CtClass cloneClass(CtClass sourceClass, String patchName, 
                                            List<CtMethod> exceptMethodList) {
    CtClass targetClass = Global.classPool.getOrNull(patchName);
    if (targetClass != null) {
        targetClass.defrost();
    }
    targetClass = Global.classPool.makeClass(patchName);
    targetClass.getClassFile().setMajorVersion(ClassFile.JAVA_7);
    targetClass.setSuperclass(sourceClass.getSuperclass());
    for (CtField field : sourceClass.getDeclaredFields()) {
        targetClass.addField(new CtField(field, targetClass));
    }
    for (CtMethod method : sourceClass.getDeclaredMethods()) {
        if (null == exceptMethodList || !exceptMethodList.contains(method)) {
            CtMethod newCtMethod = new CtMethod(method, targetClass);
            targetClass.addMethod(newCtMethod);
        }
    }
    targetClass.setModifiers(AccessFlag.clear(targetClass.getModifiers(), AccessFlag.ABSTRACT));
    return targetClass;
}

public static CtClass addPatchConstruct(CtClass patchClass, CtClass sourceClass) {
    CtField originField = new CtField(sourceClass, ORIGINCLASS, patchClass);
    originField.setModifiers(Modifier.STATIC);
    patchClass.addField(originField);
    StringBuilder patchClassConstruct = new StringBuilder();
    patchClassConstruct.append(" public Patch(Object o) {");
    patchClassConstruct.append(ORIGINCLASS + "=(" + sourceClass.getName() + ")o;");
    patchClassConstruct.append("}");
    CtConstructor constructor = CtNewConstructor.make(patchClassConstruct.toString(), patchClass);
    patchClass.addConstructor(constructor);
    return patchClass;
}

public static String getRealParametersBody() {
    StringBuilder realParameterBuilder = new StringBuilder();
    realParameterBuilder.append("public Object[] getRealParameter (Object[] args){");
    realParameterBuilder.append("    if (args == null || args.length < 1) {");
    realParameterBuilder.append("      return args;");
    realParameterBuilder.append("    }");
    realParameterBuilder.append("    Object[] realParameter = new Object[args.length];");
    realParameterBuilder.append("    for (int i = 0; i < args.length; i++) {");
    realParameterBuilder.append("      if (args[i] instanceof Object[]) {");
    realParameterBuilder.append("        realParameter[i] = getRealParameter((Object[]) args[i]);");
    realParameterBuilder.append("      } else {");
    realParameterBuilder.append("        if (args[i] ==this) {");
    realParameterBuilder.append("          realParameter[i] =this.originClass;");
    realParameterBuilder.append("        } else {");
    realParameterBuilder.append("          realParameter[i] = args[i];");
    realParameterBuilder.append("        }");
    realParameterBuilder.append("      }");
    realParameterBuilder.append("    }");
    realParameterBuilder.append("    return realParameter;");
    realParameterBuilder.append("}");
    return realParameterBuilder.toString();
}

private static void dealWithSuperMethod(CtClass patchClass,
                                      CtClass modifiedClass, String patchPath) {
    StringBuilder methodBuilder;
    List<CtMethod> invokeSuperMethodList = Global.invokeSuperMethodMap.get(modifiedClass.getName());
    if (invokeSuperMethodList == null) {
      invokeSuperMethodList = new ArrayList();
    }
    for (int index = 0; index < invokeSuperMethodList.size(); index++) {
      methodBuilder = new StringBuilder();
      if (invokeSuperMethodList.get(index).getParameterTypes().length > 0) {
        //所有的super方法前面都添加staticLede的前缀
        methodBuilder.append(
               "public static " + 
               invokeSuperMethodList.get(index).getReturnType().getName()
               + " " + 
               ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName())
               + "(" + patchClass.getName() + " patchInstance," + 
               modifiedClass.getName() + " modifiedInstance," + 
               JavaUtils.getParameterSignure(invokeSuperMethodList.get(index))
               + "){");
      } else {
        methodBuilder.append(
               "public  static  " +
               invokeSuperMethodList.get(index).getReturnType().getName()
               + "  " +
               ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName())
               + "(" + patchClass.getName() + " patchInstance,"
               + modifiedClass.getName() + " modifiedInstance){");
      }

      CtClass assistClass = PatchesAssistFactory.createAssistClass(modifiedClass,
                                  patchClass.getName(), invokeSuperMethodList.get(index));
      assistClass.writeFile(patchPath);

      if (invokeSuperMethodList.get(index).getReturnType().equals(CtClass.voidType)) {
        methodBuilder.append(
              NameManger.getInstance().getAssistClassName(patchClass.getName())
              + "." +
              ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName())
              + "(patchInstance,modifiedInstance");
      } else {
        methodBuilder.append(
              " return " + 
              NameManger.getInstance().getAssistClassName(patchClass.getName())
              + "." +
              ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName())
              + "(patchInstance,modifiedInstance");
      }
      if (invokeSuperMethodList.get(index).getParameterTypes().length > 0) {
        methodBuilder.append(",");
      }
      methodBuilder.append(
        JavaUtils.getParameterValue(invokeSuperMethodList.get(index).getParameterTypes().length)
        + ");");
      methodBuilder.append("}");
      CtMethod ctMethod = CtMethod.make(methodBuilder.toString(), patchClass);
      Global.addedSuperMethodList.add(ctMethod);
      patchClass.addMethod(ctMethod);
    }
}

处理super问题:扫描Assist类的smali代码(Smali、BakSmali),将invoke-virtual修改为invoke-super,并通过mapping.txt进行混淆(robust认为-applymapping不可靠)

加载Patch:根据包名、版本号下载patch文件,清除原有的dex,校验签名和md5值、解压为dex,新建DexClassLoader,加载dex文件、反射拿到loader,进而拿到PatchControl数组,通过读取target注解,把PatchControl设置进去

问题:目前不支持资源文件和so的修改,不支持新增字段

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值