AOP+ASM+插件化总结--实现基于注解的埋点和统计-- 代码篇之:Transform

本文深入探讨了使用ASM库进行字节码插桩的技术细节。通过Gradle插件实现,文章展示了如何在Android项目中对.class文件进行遍历和修改,包括对目录和jar文件的处理过程。详细解释了Transform类的使用方法,以及如何利用ClassReader和ClassWriter进行字节码的读写。

git地址+ASM文档
总结一下:基本都是制式的代码,包括遍历那一块等等,其他方法需要的类型和返回值也都在注释里了。

import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter

import static org.objectweb.asm.ClassReader.EXPAND_FRAMES

class MyPlugin extends Transform implements Plugin<Project> {

    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(this)

        /**
         * 对外注册api:
         android.registerTransform(new XTransform());
         android.registerTransform(new XTransform(), dependencies)
         内部注册api
         TransformManager.addTransform();
         */
    }

//    Transform中的核心方法,
//    inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
//    outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错

    @Override
    void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs,
                   TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
        println '//===============TracePlugin visit start===============//'
        //删除之前的输出
        if (outputProvider != null)
            outputProvider.deleteAll()
        //遍历input
        inputs.each { TransformInput input ->
            // 遍历文件夹
            input.directoryInputs.each {
                DirectoryInput directoryInput ->
//                    File dir = directoryInput.file
                    if (directoryInput.file.isDirectory()) {
                        //遍历文件夹
                        directoryInput.file.eachFileRecurse {
                            File file ->
                                // ...对目录进行插入字节码
                                def name = file.name
                                if (name.endsWith(".class") && !name.startsWith("R\$") &&
                                        !"R.class".equals(name) && !"BuildConfig.class".equals(name)) {
                                    println(name + "    is chang......")
                                    //这就要用到ASM插入了
                                    ClassReader classReader = new ClassReader(file.bytes)
                                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                                    def className = name.split(".class")[0]
                                    ClassVisitor cv = new TraceVisitor(className, classWriter)
                                    classReader.accept(cv, EXPAND_FRAMES)
                                    byte[] code = classWriter.toByteArray()
                                    FileOutputStream fos = new FileOutputStream(
                                            file.parentFile.absolutePath + File.separator + name)
                                    fos.write(code)
                                    fos.close()
                                    //插入结束
                                }
                        }
                    }
                    // 获取output目录 处理完输入文件之后,要把输出给下一个任务
                    def dest = outputProvider.getContentLocation(directoryInput.name,
                            directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                    // 将input的目录复制到output指定目录
                    FileUtils.copyDirectory(directoryInput.file, dest)
            }
            ////遍历jar文件 对jar不操作,但是要输出到out路径
            input.jarInputs.each {
                JarInput jarInput ->
                    if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
                        // ...对jar进行插入字节码
                    }

                    // 重命名输出文件(同目录copyFile会冲突)
                    def jarName = jarInput.name
                    println("jar = " + jarInput.file.getAbsolutePath())
                    def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                    if (jarName.endsWith(".jar")) {
                        jarName = jarName.substring(0, jarName.length() - 4)
                    }
                    def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                    FileUtils.copyFile(jarInput.file, dest)
            }
        }

    }

/**
 * Returns the unique name of the transform.
 *
 * <p>This is associated with the type of work that the transform does. It does not have to be
 * unique per variant.
 */

    @Override
    String getName() {
        return this.getClass().simpleName
    }

//转换过程中需要资源流的范围,在转换过程中不会被消耗,转换结束后, 会将资源流放回资源池去
    @Override
    Set<? super QualifiedContent.Scope> getReferencedScopes() {
        return super.getReferencedScopes()
    }

    //转换输出类型,默认是getInputTypes()
    //需要处理的数据类型,有两种枚举类型
    //CLASSES和RESOURCES,CLASSES代表处理的java的class文件,RESOURCES代表要处理java的资源
    @Override
    Set<QualifiedContent.ContentType> getOutputTypes() {
        return TransformManager.CONTENT_CLASS
    }
    /**
     * 定义了你要处理的类型
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
     * Transform的作用域,主要是三大类:SCOPE_FULL_PROJECT SCOPE_FULL_WITH_IR_FOR_DEXING  SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS
     * //    EXTERNAL_LIBRARIES        只有外部库
     * //    PROJECT                       只有项目内容
     * //    PROJECT_LOCAL_DEPS            只有项目的本地依赖(本地jar)
     * //    PROVIDED_ONLY                 只提供本地或远程依赖项
     * //    SUB_PROJECTS              只有子项目。
     * //    SUB_PROJECTS_LOCAL_DEPS   只有子项目的本地依赖项(本地jar)。
     * //    TESTED_CODE                   由当前变量(包括依赖项)测试的代码

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

    /**
     * Returns whether the Transform can perform incremental work.
     *
     * <p>If it does, then the TransformInput may contain a list of changed/removed/added files, unless
     * something else triggers a non incremental run.
     * 指明当前Transform是否支持增量编译
     */
    @Override
    boolean isIncremental() {
        return false
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值