关于使用javassist-ClassPool在gradle插件中修改某个类被重复调用的bug(缓存问题)

关于使用javassist-ClassPool在gradle插件中修改某个类被重复调用的bug(缓存问题)

背景描述

由于一些第三方库,如ARouter或者Retrofit封装得不够完善,即难以自己写一个类似的库,又在互联网上找不到类似的库。那么可以考虑动态修改这类第三方库,插入一些自己需要的代码或者监听器。相比静态修改(拉取别人的代码,再修改)的优势在于,当这类第三方库出新版时,可以平滑升级,而不需要考虑任何其它因素。(一般而言,只要你动态修改的是第三方库的public方法) 

异常现象

比如,我只想修改Postcard的navigation方法。在他在前面插入一条打印语句。结果,当先执行clean,再执行debug run,再执行rebuild时,navigation方法出现了两条打印语句。 

异常原因

产生此类异常的原因是android studio在执行上述一系统操作时,并不是每次都重新加载相关类,并运行,而是有静态缓存的。而getDefault返回的就是被静态引用的那个类。而CtClass也有自己的缓存机制。故而,上述一系统操作对某个类的修改就重复了。 

public Object navigation() {
    System.out.println("我是已经被修改过的ARouter...");
    System.out.println("我是已经被修改过的ARouter...");
    return this.navigation((Context)null);
}

问题描述

使用javassist修改某个类,示例代码如下:
下面出问题的代码就出在了:
1、调用了ClassPool pool = ClassPool.getDefault()
2、并且没有调用ctClass.detach() 

解决方案:
1、修改完CtClass后,记得调用ctClass.detach()(推荐)
2、每次需要ClassPool时使用new ClassPool(),而非使用 ClassPool.getDefault() 

    void modifyJar(JarInput jarInput,String destAbsolutePath){
        //只修改ARouter相关class
        if (!jarInput.file.absolutePath.contains("arouter-api")){
            return
        }else {
            println "准备修改jar包:from->" + jarInput.file.absolutePath + " destAbsolutePath->" + destAbsolutePath
        }
        //每次直接使用 getDefault 而不使用new ClassPool()的方式或者不使用CTClass的detach方法会导致缓存问题。
        //即,上次run时修改过的输出类,被作为下次rebuild时的输入类(由于android studio的静态变量缓存问题)
//        ClassPool pool =  new ClassPool()
        //添加java基础包
//        pool.appendSystemPath()
        ClassPool pool = ClassPool.getDefault()
        pool.insertClassPath(destAbsolutePath)
        //获取临时解压的文件路径
        File tmpDir = new File(mProject.buildDir,"myTmpDir")
        if (tmpDir.exists()){
            println "临时文件夹存在,准备删除临时文件夹 " + tmpDir
            FileUtils.deleteDirectory(tmpDir)
        }else {
            println "不存在旧的临时文件夹,不需要删除... " + tmpDir
        }
        println "准备创建临时文件夹 " + tmpDir
        tmpDir.mkdirs()
        //将原jar包解压
        Decompression.uncompress(destAbsolutePath,tmpDir.absolutePath)
        //获取准备修改的类文件
        CtClass ctClass = pool.get("com.alibaba.android.arouter.facade.Postcard")
        //对于已经冻结的class解冻之后还可以继续编辑修改
        if (ctClass.isFrozen()){
            ctClass.defrost()
        }
        //获取需要修改的方法
//        CtMethod[] ctMethods = ctClass.getDeclaredMethods()
//        println ">>>>>>>>>> ctMethods: " + ctMethods
        CtMethod ctMethod = ctClass.getDeclaredMethod("navigation")
        println ">>>>>>>>>>> ctMethod: " + ctMethod
        ctMethod.insertBefore(injectStr)
        //将修改后的Class写入到临时目录
        ctClass.writeFile(tmpDir.absolutePath)
        FileUtils.forceDelete(new File(destAbsolutePath))
        //压缩
        Compressor.compress(destAbsolutePath, tmpDir.absolutePath)
        //清除缓存(如果不清除缓存,会导致同一个class被反复修改)
//        ctClass.detach()
        println "压缩成功"
    }

猜你喜欢

转载自blog.csdn.net/ybf326/article/details/82931412