Android 热更新Robust 浅析

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;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值