Robust是新一代热更新系统,无差别兼容Android2.3-8.0版本;无需重启补丁实时生效,快速修复线上问题,补丁修补成功率高达99.9%,优势如下:
1.支持Android2.3-9.x版本
2.高兼容性、高稳定性,修复成功率高达99.9%
3.补丁实时生效,不需要重新启动
4.支持方法级别的修复,包括静态方法
5.支持增加方法和类
6.支持ProGuard的混淆、内联、优化等操作
7.需要保存打包时生成的mapping文件以及build/outputs/robust/methodsMap.robust文件
1、快速接入使用
1.生成样例apk,执行gradle命令:
./gradlew clean assembleRelease --stacktrace --no-daemon
2.安装样例apk。保存mapping.txt文件以及app/build/outputs/robust/methodsMap.robust文件
3.修改代码之后,加上**@Modify**注解或者调用RobustModify.modify()方法
4.把保存的mapping.txt和methodsMap.robust放到app/robust目录下
5.执行与生成样式apk相同的gradle命令:
./gradlew clean assembleRelease --stacktrace --no-daemon
6.补丁制作成功后会停止构建apk
7.将补丁文件copy到手机目录/sdcard/robust下
adb push ~/Desktop/code/robust/app/build/outputs/robust/patch.jar /sdcard/robust/patch.jar
补丁的路径/sdcard/robust是PatchManipulateImp中指定的
8.打开App,点击Patch按钮就会加载补丁。
9.也可以加载app/robust的样例补丁,修改了Jump_second_Activity跳转Activity的显示文字。
10.在样例中我们给类SecondActivity的方法getTextInfo(String meituan)制作补丁,你可以自行定制。
参考文献 https://github.com/Meituan-Dianping/Robust/blob/master/README-zh.md
2、autopatchbase模块
做为基类,提供基础的功能
Add
用来标记新增的类和方法
Modify
用来标记修改方法
ChangeQuickRedirect
确认当前类是否需要patch
3、 gradle-plugin模块
基础包插桩
RobustTransform.groovy
@Override
void apply(Project target) {
project = target
robust = new XmlSlurper().parse(new File("${project.projectDir}/${Constants.ROBUST_XML}"))
logger = project.logger
initConfig()
//isForceInsert 是true的话,则强制执行插入
if (!isForceInsert) {
def taskNames = project.gradle.startParameter.taskNames
def isDebugTask = false;
for (int index = 0; index < taskNames.size(); ++index) {
def taskName = taskNames[index]
logger.debug "input start parameter task is ${taskName}"
//FIXME: assembleRelease下屏蔽Prepare,这里因为还没有执行Task,没法直接通过当前的BuildType来判断,所以直接分析当前的startParameter中的taskname,
//另外这里有一个小坑task的名字不能是缩写必须是全称 例如assembleDebug不能是任何形式的缩写输入
if (taskName.endsWith("Debug") && taskName.contains("Debug")) {
// logger.warn " Don't register robust transform for debug model !!! task is:${taskName}"
isDebugTask = true
break;
}
}
if (!isDebugTask) {
project.android.registerTransform(this)
project.afterEvaluate(new RobustApkHashAction())
logger.quiet "Register robust transform successful !!!"
}
if (null != robust.switch.turnOnRobust && !"true".equals(String.valueOf(robust.switch.turnOnRobust))) {
return;
}
} else {
project.android.registerTransform(this)
project.afterEvaluate(new RobustApkHashAction())
}
}
进入apply后,首先读取配置文件 robust.xml,文件中是一些配置项,如项目是否打开Robust,是否支持progaurd,是否支持ASM进行插桩等
<?xml version="1.0" encoding="utf-8"?>
<resources>
<switch>
<!--true代表打开Robust,请注意即使这个值为true,Robust也默认只在Release模式下开启-->
<!--false代表关闭Robust,无论是Debug还是Release模式都不会运行robust-->
<turnOnRobust>true</turnOnRobust>
<!--<turnOnRobust>false</turnOnRobust>-->
<!--是否开启手动模式,手动模式会去寻找配置项patchPackname包名下的所有类,自动的处理混淆,然后把patchPackname包名下的所有类制作成补丁-->
<!--这个开关只是把配置项patchPackname包名下的所有类制作成补丁,适用于特殊情况,一般不会遇到-->
<!--<manual>true</manual>-->
<manual>false</manual>
<!--是否强制插入插入代码,Robust默认在debug模式下是关闭的,开启这个选项为true会在debug下插入代码-->
<!--但是当配置项turnOnRobust是false时,这个配置项不会生效-->
<!--<forceInsert>true</forceInsert>-->
<forceInsert>false</forceInsert>
<!--是否捕获补丁中所有异常,建议上线的时候这个开关的值为true,测试的时候为false-->
<catchReflectException>true</catchReflectException>
<!--<catchReflectException>false</catchReflectException>-->
<!--是否在补丁加上log,建议上线的时候这个开关的值为false,测试的时候为true-->
<!--<patchLog>true</patchLog>-->
<patchLog>false</patchLog>
<!--项目是否支持progaurd-->
<proguard>true</proguard>
<!--<proguard>false</proguard>-->
<!--项目是否支持ASM进行插桩,默认使用ASM,推荐使用ASM,Javaassist在容易和其他字节码工具相互干扰-->
<useAsm>true</useAsm>
<!--<useAsm>false</useAsm>-->
</switch>
<!--需要热补的包名或者类名,这些包名下的所有类都被会插入代码-->
<!--这个配置项是各个APP需要自行配置,就是你们App里面你们自己代码的包名,
这些包名下的类会被Robust插入代码,没有被Robust插入代码的类Robust是无法修复的-->
<packname name="hotfixPackage">
<name>com.meituan</name>
<name>com.sankuai</name>
<name>com.dianping</name>
</packname>
<!--不需要Robust插入代码的包名,Robust库不需要插入代码,如下的配置项请保留,还可以根据各个APP的情况执行添加-->
<exceptPackname name="exceptPackage">
<name>com.meituan.robust</name>
<name>com.meituan.sample.extension</name>
</exceptPackname>
<!--补丁的包名,请保持和类PatchManipulateImp中fetchPatchList方法中设置的补丁类名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
各个App可以独立定制,需要确保的是setPatchesInfoImplClassFullName设置的包名是如下的配置项,类名必须是:PatchesInfoImpl-->
<patchPackname name="patchPackname">
<name>com.meituan.robust.patch</name>
</patchPackname>
<!--自动化补丁中,不需要反射处理的类,这个配置项慎重选择-->
<noNeedReflectClass name="classes no need to reflect">
</noNeedReflectClass>
</resources>
transform 中根据配置使用asm,选择asm或者javaassist插入代码
if (null != robust.switch.useAsm && "false".equals(String.valueOf(robust.switch.useAsm.text()))) {
useASM = false;
}else {
//默认使用asm
useASM = true;
}
插桩处理后,所有的插桩处理过的class都会被输出的jarFile,生成/outputs/robust/methodsMap.robust
//record every method with unique method number, use LinkedHashMap to keep order for printing
public HashMap<String, Integer> methodMap = new LinkedHashMap<>();
//record method number
methodMap.put(className.replace('/', '.') + "." + name + "(" + parameters.toString() + ")", insertMethodCount.incrementAndGet());
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
logger.quiet '================robust start================'
def startTime = System.currentTimeMillis()
outputProvider.deleteAll()
File jarFile = outputProvider.getContentLocation("main", getOutputTypes(), getScopes(),
Format.JAR);
if(!jarFile.getParentFile().exists()){
jarFile.getParentFile().mkdirs();
}
if(jarFile.exists()){
jarFile.delete();
}
ClassPool classPool = new ClassPool()
project.android.bootClasspath.each {
classPool.appendClassPath((String) it.absolutePath)
}
def box = ConvertUtils.toCtClasses(inputs, classPool)
def cost = (System.currentTimeMillis() - startTime) / 1000
// logger.quiet "check all class cost $cost second, class count: ${box.size()}"
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);
writeMap2File(insertcodeStrategy.methodMap, Constants.METHOD_MAP_OUT_PATH)
logger.quiet "===robust print id start==="
for (String method : insertcodeStrategy.methodMap.keySet()) {
int id = insertcodeStrategy.methodMap.get(method);
System.out.println("key is " + method + " value is " + id);
}
logger.quiet "===robust print id end==="
cost = (System.currentTimeMillis() - startTime) / 1000
logger.quiet "robust cost $cost second"
logger.quiet '================robust end================'
}
AsmInsertImpl.java insertCode设置修饰符为public,判断是否需要插入代码。
新增类、接口、没有声明函数的类不进行插入
@Override
protected void insertCode(List<CtClass> box, File jarFile) throws IOException, CannotCompileException {
ZipOutputStream outStream = new JarOutputStream(new FileOutputStream(jarFile));
//get every class in the box ,ready to insert code
for (CtClass ctClass : box) {
//change modifier to public ,so all the class in the apk will be public ,you will be able to access it in the patch
ctClass.setModifiers(AccessFlag.setPublic(ctClass.getModifiers()));
if (isNeedInsertClass(ctClass.getName()) && !(ctClass.isInterface() || ctClass.getDeclaredMethods().length < 1)) {
//only insert code into specific classes
zipFile(transformCode(ctClass.toBytecode(), ctClass.getName().replaceAll("\\.", "/")), outStream, ctClass.getName().replaceAll("\\.", "/") + ".class");
} else {
zipFile(ctClass.toBytecode(), outStream, ctClass.getName().replaceAll("\\.", "/") + ".class");
}
}
outStream.close();
}
RobustAsmUtils.java 进行插入
public 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,
PROXYCLASSNAME,
"proxy",
"([Ljava/lang/Object;Ljava/lang/Object;" + REDIRECTCLASSNAME + "ZI[Ljava/lang/Class;Ljava/lang/Class;)Lcom/meituan/robust/PatchProxyResult;",
false);
int local = mv.newLocal(Type.getType("Lcom/meituan/robust/PatchProxyResult;"));
mv.storeLocal(local);
mv.loadLocal(local);
mv.visitFieldInsn(Opcodes.GETFIELD, "com/meituan/robust/PatchProxyResult", "isSupported", "Z");
// if isSupported
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/meituan/robust/PatchProxyResult", "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);
}
asm代码主要是PatchProxy中执行proxy、isSupport,根据PatchProxyResult结果判断执行
PatchProxy.java
public static PatchProxyResult proxy(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
PatchProxyResult patchProxyResult = new PatchProxyResult();
if (PatchProxy.isSupport(paramsArray, current, changeQuickRedirect, isStatic, methodNumber, paramsClassTypes, returnType)) {
patchProxyResult.isSupported = true;
patchProxyResult.result = PatchProxy.accessDispatch(paramsArray, current, changeQuickRedirect, isStatic, methodNumber, paramsClassTypes, returnType);
}
return patchProxyResult;
}
public static boolean isSupport(Object[] paramsArray, Object current, ChangeQuickRedirect changeQuickRedirect, boolean isStatic, int methodNumber, Class[] paramsClassTypes, Class returnType) {
//Robust补丁优先执行,其他功能靠后
if (changeQuickRedirect == null) {
//不执行补丁,轮询其他监听者
if (registerExtensionList == null || registerExtensionList.isEmpty()) {
return false;
}
for (RobustExtension robustExtension : registerExtensionList) {
if (robustExtension.isSupport(new RobustArguments(paramsArray, current, isStatic, methodNumber, paramsClassTypes, returnType))) {
robustExtensionThreadLocal.set(robustExtension);
return true;
}
}
return false;
}
String classMethod = getClassMethod(isStatic, methodNumber);
if (TextUtils.isEmpty(classMethod)) {
return false;
}
Object[] objects = getObjects(paramsArray, current, isStatic);
try {
return changeQuickRedirect.isSupport(classMethod, objects);
} catch (Throwable t) {
return false;
}
}
PatchProxyResult.java
public class PatchProxyResult {
public boolean isSupported;
public Object result;
}
示例apk 插桩后的代码
4、 auto-patch-plugin模块
AutoPatchTransform.groovy apply中进行初始化,
加载robust.xm,更新Config
加载methodsMap.robust
@Override
void apply(Project target) {
this.project = target
logger = project.logger
initConfig();
project.android.registerTransform(this)
}
def initConfig() {
//clear
NameManger.init();
InlineClassFactory.init();
ReadMapping.init();
Config.init();
ROBUST_DIR = "${project.projectDir}${File.separator}robust${File.separator}"
def baksmaliFilePath = "${ROBUST_DIR}${Constants.LIB_NAME_ARRAY[0]}"
def smaliFilePath = "${ROBUST_DIR}${Constants.LIB_NAME_ARRAY[1]}"
def dxFilePath = "${ROBUST_DIR}${Constants.LIB_NAME_ARRAY[2]}"
Config.robustGenerateDirectory = "${project.buildDir}" + File.separator + "$Constants.ROBUST_GENERATE_DIRECTORY" + File.separator;
dex2SmaliCommand = " java -jar ${baksmaliFilePath} -o classout" + File.separator + " $Constants.CLASSES_DEX_NAME";
smali2DexCommand = " java -jar ${smaliFilePath} classout" + File.separator + " -o "+Constants.PATACH_DEX_NAME;
jar2DexCommand = " java -jar ${dxFilePath} --dex --output=$Constants.CLASSES_DEX_NAME " + Constants.ZIP_FILE_NAME;
ReadXML.readXMl(project.projectDir.path);
Config.methodMap = JavaUtils.getMapFromZippedFile(project.projectDir.path + Constants.METHOD_MAP_PATH)
}
transform中,调用autoPatch,调用generatPatch。
形成patch.jar中的三个文件
PatchesInfoImpl.java
SecondActivityPatch.java
SecondActivityPatchControl.java
copyJarToRobust()将Robust\auto-patch-plugin\src\main\resources\libs
{“baksmali-2.1.2.jar”, “smali-2.1.2.jar”, “dx.jar”}进行拷贝
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
def startTime = System.currentTimeMillis()
logger.quiet '================autoPatch start================'
copyJarToRobust()
outputProvider.deleteAll()
def outDir = outputProvider.getContentLocation("main", outputTypes, scopes, Format.DIRECTORY)
project.android.bootClasspath.each {
Config.classPool.appendClassPath((String) it.absolutePath)
}
def box = ReflectUtils.toCtClasses(inputs, Config.classPool)
def cost = (System.currentTimeMillis() - startTime) / 1000
logger.quiet "check all class cost $cost second, class count: ${box.size()}"
autoPatch(box)
// JavaUtils.removeJarFromLibs()
logger.quiet '================method singure to methodid is printed below================'
JavaUtils.printMap(Config.methodMap)
cost = (System.currentTimeMillis() - startTime) / 1000
logger.quiet "autoPatch cost $cost second"
throw new RuntimeException("auto patch end successfully")
}
根据Config.supportProGuard,初始化 mapping文件 ReadMapping.getInstance().initMappingInfo();
def autoPatch(List<CtClass> box) {
File buildDir = project.getBuildDir();
String patchPath = buildDir.getAbsolutePath() + File.separator + Constants.ROBUST_GENERATE_DIRECTORY + File.separator;
clearPatchPath(patchPath);
ReadAnnotation.readAnnotation(box, logger);
if(Config.supportProGuard) {
ReadMapping.getInstance().initMappingInfo();
}
generatPatch(box,patchPath);
zipPatchClassesFile()
executeCommand(jar2DexCommand)
executeCommand(dex2SmaliCommand)
SmaliTool.getInstance().dealObscureInSmali();
executeCommand(smali2DexCommand)
//package patch.dex to patch.jar
packagePatchDex2Jar()
deleteTmpFiles()
}
def generatPatch(List<CtClass> box,String patchPath){
if (!Config.isManual) {
if (Config.patchMethodSignatureSet.size() < 1) {
throw new RuntimeException(" patch method is empty ,please check your Modify annotation or use RobustModify.modify() to mark modified methods")
}
Config.methodNeedPatchSet.addAll(Config.patchMethodSignatureSet)
InlineClassFactory.dealInLineClass(patchPath, Config.newlyAddedClassNameList)
initSuperMethodInClass(Config.modifiedClassNameList);
//auto generate all class
for (String fullClassName : Config.modifiedClassNameList) {
CtClass ctClass = Config.classPool.get(fullClassName)
CtClass patchClass = PatchesFactory.createPatch(patchPath, ctClass, false, NameManger.getInstance().getPatchName(ctClass.name), Config.patchMethodSignatureSet)
patchClass.writeFile(patchPath)
patchClass.defrost();
createControlClass(patchPath, ctClass)
}
createPatchesInfoClass(patchPath);
if (Config.methodNeedPatchSet.size() > 0) {
throw new RuntimeException(" some methods haven't patched,see unpatched method list : " + Config.methodNeedPatchSet.toListString())
}
} else {
autoPatchManually(box, patchPath);
}
}
SecondActivityPatch.java的生成逻辑
处理类的patch,克隆原来的类,增加patch构造函数,处理super方法,
private CtClass createPatchClass(CtClass modifiedClass, boolean isInline, String patchName, Set patchMethodSignureSet, String patchPath) throws CannotCompileException, IOException, NotFoundException {
List methodNoNeedPatchList = new ArrayList();
//just keep methods need patch
if (patchMethodSignureSet.size() != 0) {
for (CtMethod method : modifiedClass.getDeclaredMethods()) {
//新增方法需要保留在补丁类中
if (!Config.supportProGuard && Config.newlyAddedMethodSet.contains(method.longName)) {
continue;
}
//不是被补丁的方法
if ((!patchMethodSignureSet.contains(method.getLongName()) ||
//不是内联并且是新增的方法
(!isInline && Config.methodMap.get(modifiedClass.getName() + "." + JavaUtils.getJavaMethodSignure(method)) == null))) {
methodNoNeedPatchList.add(method);
} else {
//移除methodNeedPatchSet中需要补丁的方法,留在补丁类中的方法默认全部会被处理
Config.methodNeedPatchSet.remove(method.getLongName());
}
}
}
CtClass temPatchClass = cloneClass(modifiedClass, patchName, methodNoNeedPatchList);
if (temPatchClass.getDeclaredMethods().length == 0) {
printList(patchMethodSignureSet.toList());
throw new RuntimeException("all methods in patch class are deteted,cannot find patchMethod in class " + temPatchClass.getName());
}
JavaUtils.addPatchConstruct(temPatchClass, modifiedClass);
CtMethod reaLParameterMethod = CtMethod.make(JavaUtils.getRealParamtersBody(), temPatchClass);
temPatchClass.addMethod(reaLParameterMethod);
dealWithSuperMethod(temPatchClass, modifiedClass, patchPath);
if (Config.supportProGuard && ReadMapping.getInstance().getClassMapping(modifiedClass.getName()) == null) {
throw new RuntimeException(" something wrong with mappingfile ,cannot find class " + modifiedClass.getName() + " in mapping file");
}
List<CtMethod> invokeSuperMethodList = Config.invokeSuperMethodMap.getOrDefault(modifiedClass.getName(), new ArrayList<>());
createPublicMethodForPrivate(temPatchClass);
for (CtMethod method : temPatchClass.getDeclaredMethods()) {
// shit !!too many situations need take into consideration
// methods has methodid and in patchMethodSignatureSet
if (!Config.addedSuperMethodList.contains(method) && reaLParameterMethod != method && !method.getName().startsWith(Constants.ROBUST_PUBLIC_SUFFIX)) {
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())) {
/*
原来只判断 invokeSuperMethodList.contains(m.getMethod()) 为true就执行invokeSuperString是有bug的。
CtMethod的hashcode用getStringRep()实现,等于只有根据函数名做匹配
碰到这么一个情况,如下所示的修复代码
@Modify
@Override
public void onBackPressed() {
if (mDispatchTouchEventHook != null && mDispatchTouchEventHook.onBackPressed()) {
return;
}
postFeedPosition();
checkTaskRoot();
super.onBackPressed();
}
mDispatchTouchEventHook的onBackPressed()方法也被判定为调用super方法了。
然而activity的onBackPressed()是返回值是void,
mDispatchTouchEventHook的onBackPressed()方法返回值是boolean,直接导致打不出patch
即便能打出patch,把这里针对mDispatchTouchEventHook的调用换成super调用也容易触发其他的bug
*/
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();
}
}
});
}
}
//remove static code block,pay attention to the class created by cloneClassWithoutFields which construct's
CtClass patchClass = cloneClassWithoutFields(temPatchClass, patchName, null);
patchClass = JavaUtils.addPatchConstruct(patchClass, modifiedClass);
return patchClass;
}
5、 patch模块
app模块中MainActivity.java 点击进入patch模块
private void runRobust() {
new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBackSample()).start();
}
public class PatchExecutor extends Thread {
protected Context context;
protected PatchManipulate patchManipulate;
protected RobustCallBack robustCallBack;
public PatchExecutor(Context context, PatchManipulate patchManipulate, RobustCallBack robustCallBack) {
this.context = context.getApplicationContext();
this.patchManipulate = patchManipulate;
this.robustCallBack = robustCallBack;
}
@Override
public void run() {
try {
//拉取补丁列表
List<Patch> patches = fetchPatchList();
//应用补丁列表
applyPatchList(patches);
} catch (Throwable t) {
Log.e("robust", "PatchExecutor run", t);
robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
}
}
DexClassLoader 加载patch类,设置changeQuickRedirectField
protected boolean patch(Context context, Patch patch) {
if (!patchManipulate.verifyPatch(context, patch)) {
robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
return false;
}
ClassLoader classLoader = null;
try {
File dexOutputDir = getPatchCacheDirPath(context, patch.getName() + patch.getMd5());
classLoader = new DexClassLoader(patch.getTempPath(), dexOutputDir.getAbsolutePath(),
null, PatchExecutor.class.getClassLoader());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
if (null == classLoader) {
return false;
}
Class patchClass, sourceClass;
Class patchesInfoClass;
PatchesInfo patchesInfo = null;
try {
Log.d("robust", "patch patch_info_name:" + patch.getPatchesInfoImplClassFullName());
patchesInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
patchesInfo = (PatchesInfo) patchesInfoClass.newInstance();
} catch (Throwable t) {
Log.e("robust", "patch failed 188 ", t);
}
if (patchesInfo == null) {
robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
return false;
}
//classes need to patch
List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
if (null == patchedClasses || patchedClasses.isEmpty()) {
// robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
//手写的补丁有时候会返回一个空list
return true;
}
boolean isClassNotFoundException = false;
for (PatchedClassInfo patchedClassInfo : patchedClasses) {
String patchedClassName = patchedClassInfo.patchedClassName;
String patchClassName = patchedClassInfo.patchClassName;
if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
continue;
}
Log.d("robust", "current path:" + patchedClassName);
try {
try {
sourceClass = classLoader.loadClass(patchedClassName.trim());
} catch (ClassNotFoundException e) {
isClassNotFoundException = true;
// robustCallBack.exceptionNotify(e, "class:PatchExecutor method:patch line:258");
continue;
}
Field[] fields = sourceClass.getDeclaredFields();
Log.d("robust", "oldClass :" + sourceClass + " fields " + fields.length);
Field changeQuickRedirectField = null;
for (Field field : fields) {
if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), sourceClass.getCanonicalName())) {
changeQuickRedirectField = field;
break;
}
}
if (changeQuickRedirectField == null) {
robustCallBack.logNotify("changeQuickRedirectField is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
Log.d("robust", "current path:" + patchedClassName + " something wrong !! can not find:ChangeQuickRedirect in" + patchClassName);
continue;
}
Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
try {
patchClass = classLoader.loadClass(patchClassName);
Object patchObject = patchClass.newInstance();
changeQuickRedirectField.setAccessible(true);
changeQuickRedirectField.set(null, patchObject);
Log.d("robust", "changeQuickRedirectField set success " + patchClassName);
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
}
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
// robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
}
}
Log.d("robust", "patch finished ");
if (isClassNotFoundException) {
return false;
}
return true;
}