- 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的修改,不支持新增字段