框架手写系列---Asm方式实现日志插入

asm与javassist作为修改字节码文件的两种常用方式,在编译过程中,可以动态修改class字节码文件。前文已用javassist方式实现热修复的前置步骤,本文用asm实现动态的给每个activity插入Log日志。

一、插件的生成与依赖导入

1、用该文章中的方法:Android中Plugin插件工程的自动生成 自动生成一个android的插件模板;

2、加入asm依赖,加入依赖后的插件build的格式如下:

dependencies{
    compile gradleApi()
    compile localGroovy()
    implementation 'com.android.tools.build:gradle:3.6.3'
    //asm依赖
    implementation 'org.ow2.asm:asm:7.1'
    implementation 'org.ow2.asm:asm-commons:7.1'
}

在最外层的build中,添加依赖如下:

dependencies {
        classpath 'com.android.tools.build:gradle:3.6.3'
        //asm路径
        classpath 'com.sunny.asmplugin:asmplugin:1.0.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

二、插件的注册与编写

修改AsmPlugin文件如下,该文件将SunnyAsmTransForm注册到工程中。

public class AsmPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        println '----------- registering SunnyAsmTransForm  -----------'
        SunnyAsmTransForm transform = new SunnyAsmTransForm()
        android.registerTransform(transform)
        println '----------- registered SunnyAsmTransForm  -----------'
    }
}

SunnyAsmTransForm负责实现具体逻辑:

package com.sunny.asmplugin

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import groovy.io.FileType
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter

/**
 * @author shenxiao* @version V1.0* @package com.sunny.easyuse* @date 2020/6/22 4:34 PM
 */
public class SunnyAsmTransForm extends Transform {

    SunnyAsmTransForm() {

    }

    @Override
    public String getName() {
        return "SunnyAsmTransForm"
    }

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

    @Override
    public Set<QualifiedContent.Scope> getScopes() {
        return TransformManager.PROJECT_ONLY
    }

    @Override
    public boolean isIncremental() {
        return false;
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)

        //拿到所有的class文件
        Collection<TransformInput> transformInputs = transformInvocation.inputs
        TransformOutputProvider outputProvider = transformInvocation.outputProvider
        if (outputProvider != null) {
            outputProvider.deleteAll()
        }

        transformInputs.each { TransformInput transformInput ->
            // 遍历directoryInputs(文件夹中的class文件) directoryInputs代表着以源码方式参与项目编译的所有目录结构及其目录下的源码文件
            // 比如我们手写的类以及R.class、BuildConfig.class以及MainActivity.class等
            transformInput.directoryInputs.each { DirectoryInput directoryInput ->
                File dir = directoryInput.file
                if (dir) {
                    dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) { File file ->
                        System.out.println("find class: " + file.name)
                        //对class文件进行读取与解析
                        ClassReader classReader = new ClassReader(file.bytes)
                        //对class文件的写入
                        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                        //访问class文件相应的内容,解析到某一个结构就会通知到ClassVisitor的相应方法
                        ClassVisitor classVisitor = new SunnyClassVisitor(classWriter)
                        //依次调用 ClassVisitor接口的各个方法
                        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
                        //toByteArray方法会将最终修改的字节码以 byte 数组形式返回。
                        byte[] bytes = classWriter.toByteArray()

                        //通过文件流写入方式覆盖掉原先的内容,实现class文件的改写。
                        //FileOutputStream outputStream = new FileOutputStream( file.parentFile.absolutePath + File.separator + fileName)
                        FileOutputStream outputStream = new FileOutputStream(file.path)
                        outputStream.write(bytes)
                        outputStream.close()
                    }
                }

                //处理完输入文件后把输出传给下一个文件
                def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes,
                        directoryInput.scopes, Format.DIRECTORY)
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
        }
    }
}

上文中涉及到的SunnyClassVisitor与SunnyMethodVisitor,是实现修改字节码的具体文件:

SunnyClassVisitor中指定了需要访问的方法:

package com.sunny.asmplugin;

/**
 * @author shenxiao
 * @version V1.0
 * @package com.sunny.asmplugin
 * @date 2020/9/8 11:41 AM
 */
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class SunnyClassVisitor extends ClassVisitor {
    private String className;
    private String superName;

    public SunnyClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.className = name;
        this.superName = superName;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("ClassVisitor visitMethod name-------" + name + ", superName is " + superName);
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);

        if (superName.equals("android/support/v7/app/AppCompatActivity")
            || superName.equals("androidx/appcompat/app/AppCompatActivity") ) {
            if (name.startsWith("onCreate")) {
                //处理onCreate()方法
                return new SunnyMethodVisitor(mv, className, name);
            }
        }
        return mv;
    }

    @Override
    public void visitEnd() {
        super.visitEnd();
    }
}

SunnyMethodVisitor实现方法中修改字节码逻辑:

package com.sunny.asmplugin;

/**
 * @author shenxiao
 * @version V1.0
 * @package com.sunny.asmplugin
 * @date 2020/9/8 11:42 AM
 */

import org.objectweb.asm.MethodVisitor;
        import org.objectweb.asm.Opcodes;

public class SunnyMethodVisitor extends MethodVisitor {
    private String className;
    private String methodName;

    public SunnyMethodVisitor(MethodVisitor methodVisitor, String className, String methodName) {
        super(Opcodes.ASM5, methodVisitor);
        this.className = className;
        this.methodName = methodName;
    }

    //方法执行前插入
    @Override
    public void visitCode() {
        super.visitCode();
        System.out.println("MethodVisitor visitCode------");

        mv.visitLdcInsn("TAG");
        mv.visitLdcInsn(className + "---->" + methodName);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
        mv.visitInsn(Opcodes.POP);
    }
}

到此完成了插件编写,将插件上传到本地后,可应用插件。

三、插件的使用

新建工程,工程中含MainActivity文件。在工程的build中,加上:

apply plugin: 'com.sunny.asmplugin'

编译工程后,可在app/build/intermediates/transforms/SunnyAsmTransForm/debug/0/com/sunny/asm/MainActivity.class中查看插入的日志代码:

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        //此处为asm新增的日志代码
        Log.i("TAG", "com/sunny/asm/MainActivity---->onCreate");
        super.onCreate(savedInstanceState);
        this.setContentView(2131296284);
    }
}

至此,完成asm动态修改字节码逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值