使用ASM修复三方jar报错

在bugly上面看到老是报下面的错,但是我们自测,包括给测试进行测试,也不会出现下面的问题,很是头疼


经过三方jar进行分析发现就是下面图片的地方mContext报错,那么我们是不是可以在这个getNetworkType方法里插入mContext的判空操作呢?

但是这个只是解决这一个方法,这个类还是有其他方法里也用到了这个mContext,我们是不是可以直接在最开始传入context的地方插入判空呢?于是继续看源码发现可以在init方法里插入

可以插入如下代码:

public void init() {
    if(mContext != null){
        Log.e("zuojie","mContext is null");
        return ;
    }
    // 原本的逻辑代码
}

这时就可以使用之前了解过的ASM框架,对三方jar的class进行部分修改

1、创建buildScr目录

在项目中新建buildScr目录,然后添加build.gradle文件

apply plugin: 'groovy'
apply plugin: 'java'
apply plugin: 'maven'

repositories {
    maven { url 'https://maven.aliyun.com/repository/public'}
    maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
    maven { url 'https://maven.aliyun.com/repository/google' }
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
    google()
//    jcenter()
    mavenCentral()
}
dependencies {
    implementation gradleApi()
    //因为我这个自定义Plugin是用java写的不是Groovy所以这个localGroovy不需要
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:4.2.1'
    implementation 'org.ow2.asm:asm:7.1'
    implementation 'org.ow2.asm:asm-commons:7.1'
}

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

tasks.withType(JavaCompile){
    options.encoding = "UTF-8"
}

注意
上面的com.android.tools.build:gradle:4.2.1要跟你项目中根目录下的build.gradle要一致

2、创建自定义的Plugin插件

class ModifyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println "=====Hello TransformPlugin"
        def android = project.extensions.getByType(AppExtension)
        //注册 Transform,操作 class 文件
        android.registerTransform(new ModifyTransform())
    }
}

在项目的build.gradle中引入apply plugin: ModifyPlugin

3、创建自定义Transform

在自定义一个Transform用于对三方jar进行遍历得到具体的class文件

class ModifyTransform extends Transform {

    //自定义 Task 名称
    @Override
    String getName() {
        return this.getClass().name
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    // 当前Transform是否支持增量编译
    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()
        //清理文件
        outputProvider.deleteAll();

        transformInvocation.inputs.each {
            TransformInput input ->

                //这里存放着开发者手写的类
                input.directoryInputs.each {
                    DirectoryInput directoryInput ->
                        File dir = directoryInput.file
                        println("dir===" + dir)
                        /* dir.eachFileRecurse {
                             File file ->
                                 def name = file.name
                                 if(name.endsWith(".class") && !name.startsWith("R\$") &&
                                         !"R.class".equalsIgnoreCase(name) && !"BuildConfig.class".equalsIgnoreCase(name)){
                                     println("name==="+name)
                                     //class阅读器
                                     ClassReader cr = new ClassReader(file.bytes);
                                     //写出器
                                     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                                     //分析,处理结果写入cw
                                     cr.accept(new ClassAdapterVisitor(cw),ClassReader.EXPAND_FRAMES)
                                     byte[] newClassBytes = cw.toByteArray();
                                     FileOutputStream fos = new FileOutputStream(file.parentFile.absolutePath+File.separator+name)
                                     fos.write(newClassBytes)
                                     fos.close()
                                 }
                        }*/

                        //获取output目录,处理完输入文件之后,要把输出给下一个任务
                        def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes,
                                directoryInput.scopes, Format.DIRECTORY)
                        //将input的目录复制到output指定目录
                        FileUtils.copyDirectory(directoryInput.file, dest)
                }

                //遍历jar文件
                input.jarInputs.each {
                    JarInput jarInput ->
                        def src = jarInput.file
                        String jarName = src.name
                        String absolutePath = src.absolutePath
                        if (jarName.contains("bi-9.1-runtime")) {
                            println("jarName = ${jarName}")
                            println("jar = ${absolutePath}")
                        }
                        String md5name = DigestUtils.md5Hex(src.getAbsolutePath());
                        if (absolutePath.endsWith(".jar")) {
                            //...对jar进行插入字节码
                            jarName = jarName.substring(0, jarName.length() - 4)
                        }

                        // bi-9.1-runtime.jar
                        File dest = outputProvider.getContentLocation(jarName + md5name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                        if (jarName.contains("bi-9.1-runtime")) {
                            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(dest))
                            JarFile jarFile = new JarFile(src)
                            Enumeration<JarEntry> entries = jarFile.entries()
                            while (entries.hasMoreElements()) {
                                JarEntry jarEntry = entries.nextElement()
                                def jarEntryName = jarEntry.name
                                println "====jarEntryName:$jarEntryName"
                                ZipEntry zipEntry = new ZipEntry(jarEntryName)

                                if(zipEntry.isDirectory()) continue

                                jarOutputStream.putNextEntry(zipEntry);

                                //读取class的字节数据
                                 InputStream is = jarFile.getInputStream(jarEntry)

                                ByteArrayOutputStream bos = new ByteArrayOutputStream()
                                IOUtils.copy(is, bos)
                                byte[] sourceClassBytes = bos.toByteArray()
                                is.close()
                                bos.close()

                                 if ("bi/com/xxx/bi/GetBaseDataInfo.class" == jarEntryName) {
                                    println "55555"
                                     //class阅读器
                                     ClassReader cr = new ClassReader(bos.toByteArray());
                                     //写出器
                                     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                                     //分析,处理结果写入cw
                                     cr.accept(new ClassAdapterVisitor(cw),ClassReader.EXPAND_FRAMES);
                                     byte[] newClassBytes = cw.toByteArray();
                                     jarOutputStream.write(newClassBytes)
                                }else {
                                    jarOutputStream.write(sourceClassBytes)
                                }
                            }
                            jarOutputStream.close()
                        } else {
                            FileUtils.copyFile(src, dest)
                        }
                }
        }
    }
}

4、基于ASM创建class文件的操作类

需要根据ASM框架提供的api和规则来创建操作字节码文件的操作类,这里需要修改字节码来实现自己的业务逻辑,所以为了方便这里使用java类来实现,ClassAdapterVisitor类用于对class字节码的观察并监听类的信息,ClassAdapterVisitor代码如下:

public class ClassAdapterVisitor extends ClassVisitor {
    //当前类的类名称
    //本例:com/zxj/plugin/demo/MainActivity
    private String className;

    //className类的父类名称
    //本例:androidx/appcompat/app/AppCompatActivity
    private String superName;

    public ClassAdapterVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM7, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        className = name;
        System.out.println("className:"+name+",superName:"+superName+",interfaces.length:"+interfaces.length);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        System.out.println("====access:"+access+",name:"+name+",descriptor:"+descriptor+",signature:"+signature+",value:"+value);
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        System.out.println("methodName:"+name+",descriptor:"+descriptor+",signature:"+signature);

        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);

        if("init".equals(name) && "()V".equals(descriptor)){
            return new MethodAdapterVisitor(Opcodes.ASM7,mv,access,name,descriptor,className);
        }
        return mv;
    }
}

MethodAdapterVisitor类用于对方法的观察,可以在对应的方法执行前插入自己的代码

public class MethodAdapterVisitor extends AdviceAdapter {
    private String methodName;
    private String className;

    public MethodAdapterVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor,String className) {
        super(api, methodVisitor, access, name, descriptor);
        this.className = className;
        methodName = name;
        System.out.println("MethodAdapterVisitor->MethodName:"+name);
    }

    @Override
    protected void onMethodEnter() {
        super.onMethodEnter();
        /**
         * if (this.mContext == null) {
         *     Log.e("zuojie", "mContext is null");
         *     return;
         * }
         */
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, "bi/com/xxx/bi/GetBaseDataInfo", "mContext", "Landroid/content/Context;");
        Label label1 = new Label();
        mv.visitJumpInsn(IFNONNULL, label1);// IFNULL IFNONNULL
        Label label2 = new Label();
        mv.visitLabel(label2);
        mv.visitLdcInsn("zuojie");
        mv.visitLdcInsn(className+",mContext is null");
        mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
        mv.visitInsn(POP);
        Label label3 = new Label();
        mv.visitLabel(label3);
        mv.visitInsn(RETURN);
        mv.visitLabel(label1);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
    }
}

这里怎么写上面的插入语句呢?其实是可以利用AS的一个插件ASM Bytecode Viewer Support Kotlin,其中图片上面前两个在新版本AS是不能正常使用了。

我们可以创建一个测试的类,这个类里的模仿三方jar里的方法,


然后右键选择ASM Bytecode Viewer,就可以在AS的右上角查看字节码

我们可以把生成的字节码拷贝到自己的项目中,稍做修改一下就可以了,

注意:修改时一定要仔细,有一点没修改好,在编译的时候就会报错,而且报的错还看不出来哪里的错。

下面就是反编译apk后,查看可以发现已经插入成功了。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容