概况可见:github.com/stewForAni/…

编译环境

  • Android Studio Flamingo | 2022.2.1 Patch 1
  • GV6.7.1 / AGP4.2.2
  • Java1.8

常见功能

  • 日志埋点
  • 性能监控
  • 登录校验

Tips

  • 需求对jar包进行处理(即R.class的改动)
  • 对android版别和gradle版别要求比较严苛,java版主张在低gradle版别下进行
  • 使用AS插件ASM Bytecode Viewer Support Kotlin,ASM Bytecode Viewer无效

使用过程

1.创立Gradle Plugin

  1. 创立library类型的module,除build.gradle和main文件夹,其余都删除
  2. main中创立groovy和java文件夹,在groovy中创立XXX.groovy文件,也能够添加文件夹途径再创立XXX.groovy文件,差异在于直接创立不需求写package
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class AsmLifePlugin implements Plugin<Project>{
  void apply(Project project) {
    System.out.println("#------AsmLifePlugin-------#")
  }
}
package asm.life.plugin
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class AsmLifePlugin implements Plugin<Project>{
  void apply(Project project) {
    System.out.println("@------AsmLifePlugin-------@")
  }
}
  1. main中创立resources/META-INF/gradle-plugins/ABC.properties,途径有必要这样,ABC能够随意,即插件名字,文件内容如下(差异和上面对应):
  • //implementation-class=asm.life.plugin.AsmLifePlugin
  • //implementation-class=AsmLifePlugin
  1. build.gralle如下:
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation gradleApi()
  implementation localGroovy()
  implementation 'com.android.tools.build:gradle:4.2.2'
}
group='lifeplugin'
version='1.0.0'
uploadArchives {
  repositories {
    mavenDeployer {
      //本地的Maven地址设置
      repository(url: uri('../asm_life_repo'))
    }
  }
}
  1. 履行右侧gradle中的uploadArchives
  2. app module的buildgradle如下:
apply plugin: 'ABC'
buildscript {
  repositories {
    google()
    mavenCentral()
    //自定义插件maven地址
    maven { url '../asm_life_repo' }
  }
  dependencies {
    //加载自定义插件 group + module + version
    classpath 'lifeplugin:asm_life_plugin:1.0.0'
  }
}

如需改变插件装备,需求注释6中的代码

  1. 履行右侧gradle中的assemble,成果如下:
Configure project :app
#------AsmLifePlugin-------#

2.使用自定义Transform找到一切class文件

插件装备完之后需求使用Transform的api来找到class文件

Transform解说(摘自参阅文章):

Transform 能够被看作是 Gradle 在编译项目时的一个 task
在 .class 文件转化成 .dex 的流程中会履行这些 task
对一切的 .class 文件(可包含第三方库的 .class)进行转化
转化的逻辑定义在 Transformtransform 办法中
实际上平时咱们在 build.gradle 中常用的功能都是经过 Transform 完成的
比如混杂(proguard)、分包(multi-dex)

创立自定义Transform,同样也是groovy文件

public class AsmLifeTrans extends Transform{}

需求完成抽象类 Transform 中的抽象办法,Transform 首要作用是检索项目编译过程中的一切文件。经过这几个办法,咱们能够对自定义 Transform 设置一些遍历规矩,具体如下:

  • getName
    设置咱们自定义的 Transform 对应的 Task 名称。Gradle 在编译的时分,会将这个名称显现在控制台上。比如:Task:app:transformClassesWithXXXForDebug。

  • getInputType
    在项目中会有各种各样格式的文件,经过 getInputType 能够设置 AsmLifeTrans 接收的文件类型,此办法回来的类型是 Set<QualifiedContent.ContentType> 集合。ContentType 有以下 2 种取值,CLASSES:代表只检索 .class 文件;RESOURCES:代表检索 java 规范资源文件。

  • getScopes()
    这个办法规则自定义 Transform 检索的规模

  • isIncremental()
    表明当时 Transform 是否支持增量编译

  • transform()
    在 自定义Transform 中最重要的办法就是 transform()。在这个办法中,能够获取到两个数据的流向。inputs:inputs 中是传过来的输入流,其中有两种格式,一种是 jar 包格式,一种是 directory(目录格式)。outputProvider:outputProvider 获取到输出目录,最终将修正的文件复制到输出目录,这一步有必要做,否则编译会报错。

public class AsmLifeTrans extends Transform {
  @Override
  String getName() {
    return "AsmLifeTrans"
  }
  @Override
  Set<QualifiedContent.ContentType> getInputTypes() {
    return TransformManager.CONTENT_CLASS
  }
  @Override
  Set<? super QualifiedContent.Scope> getScopes() {
    return TransformManager.PROJECT_ONLY
  }
  @Override
  boolean isIncremental() {
    return false
  }
  @Override
  void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
    transformInvocation.inputs.each {
      it.directoryInputs.each {
        System.out.println("find class: " + it.file.name)
      }
    }
  }
}
public class AsmLifePlugin implements Plugin<Project>{
  void apply(Project project) {
    System.out.println( "###------AsmLifePlugin-------###")
    def android = project.extensions.getByType(AppExtension)
    AsmLifeTrans trans = new AsmLifeTrans()
    android.registerTransform(trans)
  }
}

履行右侧gradle中的app内的build,能够看到一切class文件

3.遍历class文件,找到方针class,经过asm注入字节码

ASM是开源结构,常用API如下(摘自参阅文章):

  • ClassReader:担任解析 .class 文件中的字节码,并将一切字节码传递给 ClassWriter。
  • ClassVisitor:担任拜访 .class 文件中各个元素,当解析到某些特定结构时(比如类变量、办法),它会自动调用内部相应的 FieldVisitor 或许 MethodVisitor 的办法,进一步解析或许修正 .class 文件内容。
  • ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,担任将修正后的字节码输出为 byte 数组

在 asm_life_plugin 的 build.gradle 中,添加对 ASM 的依靠

implementation 'org.ow2.asm:asm:9.1'
implementation 'org.ow2.asm:asm-commons:9.1'

在java文件夹内创立文件:
//处理class

public class ActivityLifeClassVisitor extends ClassVisitor {
    private String cName;
    private String sName;
    public ActivityLifeClassVisitor(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);
        cName = name;
        sName = superName;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
        if (sName.equals("androidx/appcompat/app/AppCompatActivity")) {
            if (name.startsWith("onCreate")) {
                //处理onCreate()办法
                return new ActivityLifeMethodVisitor(mv, cName, name);
            }
        }
        return mv;
    }
}

//处理method

package com.life.jv;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class ActivityLifeMethodVisitor extends MethodVisitor {
   private String cName;
   private String mName;
   public ActivityLifeMethodVisitor(MethodVisitor mv, String className, String name) {
      super(Opcodes.ASM5,mv);
      cName = className;
      mName = name;
   }
   @Override
   public void visitCode() {
      super.visitCode();
      //使用AS插件ASM Bytecode Viewer Support Kotlin来查看需求刺进的字节码
      mv.visitVarInsn(Opcodes.ALOAD,0);
      mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
      mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/stew/androidtest/util/AppLogUtil", "addLifeLog", "(Ljava/lang/String;)V", false);
   }
}

留意点:java文件夹内的文件需求树立相应的途径,比如:com.xxx.asmlife.xxxxxx.java


ASM api参阅:

  • visitInsn(int):拜访一个零参数要求的字节码指令,如ACONST_NULL
  • visitIntInsn(int, int):拜访一个需求零操作栈要求但需求有一个int参数的字节码指令,如BIPUSH
  • visitVarInsn(int, int):拜访一个有关于局部变量的字节码指令,如ALOAD
  • visitTypeInsn(int, String):拜访一个有关于类型的字节码指令,如CHECKCAST
  • visitFieldInsn(int, String, String, String):拜访一个有关于字段的字节码,如PUTFIELD
  • visitMethodInsn(int, String, String, String, boolean):拜访一个有关于办法调用的字节码,如INVOKESPECIAL
  • visitJumpInsn(int, Label):拜访跳转字节码,如IFEQ之后,是一些被包装好的字节码拜访办法,这些办法都根据最基本的字节码指令,但是不需求咱们自己用上面提到的那些办法直接调用字节码。
  • visitInvokeDynamicInsn(String, String, Handle, Object…):根据INVOKEDYNAMIC,动态办法调用,会在lambda表达式和办法引证里边说到
  • visitLdcInsn(Object):根据LDC、LDC_W和LDC2_W,将一个常量加载到操作栈用(具体见下文)
  • visitIincInsn(int, int):根据IINC、IINC_W,自增/减表达式
  • visitTableSwitchInsn(int, int, Label, Label…):根据TABLESWITCH,用于进行table-switch操作
  • visitLookupSwitchInsn(Label, int[], Label[]):根据LOOKUPSWITCH,用于进行lookup-switch操作
  • visitMultiANewArrayInsn(String, int):根据MULTIANEWARRAY,用于创立多重维度数组,如int[][]

参阅文章:

  • kaiwu.lagou.com/course/cour…
  • www.jianshu.com/p/eb15847ca…