每个方法增加日志输出,记录调用时间
使用Gradle自定义一个插件,并且在代码编译阶段,使用ASM在Transform中进行代码插入。
一、gradle插件
1. 创建module和groovy目录

2. 定义build.gradle
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//gradle和groovy的依赖
implementation gradleApi()
implementation localGroovy()
}
dependencies {
//直接使用build工具的asm类库,不再额外引入asm的依赖
implementation 'com.android.tools.build:gradle:3.5.3'
}
repositories {
mavenCentral()
}
//本地配置的参数,需要上传maven时配置用户名和密码等信息
Properties props = new Properties()
props.load(project.rootProject.file('local.properties').newDataInputStream())
def artifactory_user = props.getProperty('user')
def pwd = props.getProperty('password')
def contextUrl = props.getProperty('contextUrl')
def repoKey = props.getProperty('repoKey')
def releaseRepoKey = props.getProperty('release_repoKey')
def snapshot = true
def log_version = '1.0.0'
def log_group = 'com.example.autolog'
def log_id = 'auto-log'
group = log_group
version = log_version
def debug = true
uploadArchives {
repositories {
mavenDeployer {
if (debug) {
//本地仓库,生成的jar和pom放在根目录下的repo-local目录中
repository(url: "file://$projectDir/../repo-local")
} else {
def repokey = snapshot ? repoKey : releaseRepoKey
repository(url: "${contextUrl}/${repokey}") {
authentication(username: artifactory_user, password: pwd)
}
}
pom.groupId = log_group
pom.artifactId = log_id
pom.version = log_version
pom.project {
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
}
}
}
3. 创建Plugin
AutoLogPlugin.groovy:
package com.example.autolog
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
//实现Plugin接口
public class AutoLogPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//判断项目使用了com.android.application插件
def isApp = project.plugins.hasPlugin(AppPlugin)
if(isApp){
println 'project('+project.name+') apply auto-log plugin'
def android = project.extensions.getByType(AppExtension)
def transform = new LogTransform(project)
//注册Transform
android.registerTransform(transform)
project.afterEvaluate {
//do init
}
}
}
}
4. 创建LogTransform
package com.example.autolog
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import org.gradle.api.Project
import org.objectweb.asm.*
import org.objectweb.asm.util.TraceClassVisitor
public class LogTransform extends Transform {
Project project;
LogTransform(Project project) {
this.project = project
}
@Override
String getName() {
return 'auto-log'
}
//处理类型:java字节码会被处理
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
//处理范围
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
//是否支持增量编译
return false
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
project.logger.warn('start auto-log transform..')
if (!transformInvocation.incremental) {
transformInvocation.outputProvider.deleteAll()
}
transformInvocation.inputs.each { TransformInput input ->
input.jarInputs.each { JarInput jarInput ->
//遍历jar文件
scanJar(jarInput, transformInvocation.outputProvider)
}
input.directoryInputs.each { DirectoryInput directoryInput ->
//遍历源码目录
project.logger.warn("directoryInput : " + directoryInput.name)
directoryInput.file.eachFileRecurse { File file ->
project.logger.warn("directory file:"+file.name)
if (file.isFile()) {
//如果是字节码文件,进行处理
scanClass(file)
}
}
}
}
}
void scanJar(JarInput jarInput, TransformOutputProvider outputFileProvider) {
}
void scanClass(File file) {
project.logger.warn("scanClass " + file.name)
def optclass = new File(file.getParent(), file.name + ".opt")
FileInputStream is = new FileInputStream(file)
FileOutputStream os = new FileOutputStream(optclass)
//返回处理后的字节码,写入文件
def bytes = generateCode(is,file.name)
os.write(bytes)
is.close()
os.close()
//删除原有的编译后的class,将新生成的class重命名
if (file.exists()) {
file.delete()
}
optclass.renameTo(file)
}
byte[] generateCode(InputStream is, String name) {
ClassReader cr = new ClassReader(is)
ClassWriter cw = new ClassWriter(cr, 0)
//ASM进行方法扫描的类
ClassVisitor cv = new LogClassVisitor(Opcodes.ASM5, cw, name)
//TraceClassVisitor可以将扫描过的类的字节码保存到本地
TraceClassVisitor tcv = new TraceClassVisitor(cv, new PrintWriter(new File("E:/ASM/" + name + ".txt")))
cr.accept(tcv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()
}
class LogClassVisitor extends ClassVisitor {
String cn
boolean isInterface
LogClassVisitor(int api) {
super(api)
}
LogClassVisitor(int api, ClassVisitor cv, String className) {
super(api, cv)
this.cn = className
}
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
//类是否是接口
isInterface = access == Opcodes.ACC_INTERFACE
}
@Override
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
//如果不是构造函数,且不是接口,则进行方法处理
if(mv != null && name !="<init>" && !isInterface){
return new LogMethodVisitor(Opcodes.ASM5, mv, name, cn)
}
return mv
}
}
class LogMethodVisitor extends MethodVisitor {
String mn
String cn
LogMethodVisitor(int api) {
super(api)
}
LogMethodVisitor(int api, MethodVisitor mv, String methodName, String className) {
super(api, mv)
this.mn = methodName
this.cn = className
}
@Override
void visitCode() {
//使用ASM接口进行方法注入
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
mv.visitVarInsn(Opcodes.LSTORE, 1)
mv.visitLdcInsn(cn)
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")
mv.visitInsn(Opcodes.DUP)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder","<init>","()V", false)
mv.visitLdcInsn(mn + " start")
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitVarInsn(Opcodes.LLOAD, 1)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;",false)
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "v", "(Ljava/lang/String;Ljava/lang/String;)I", false)
mv.visitInsn(Opcodes.POP)
super.visitCode()
}
@Override
void visitMaxs(int maxStack, int maxLocals) {
//帧的最大栈长度,和本地变量的数量,可以查看字节码进行比对
super.visitMaxs(maxStack + 4, maxLocals + 2)
}
}
}
5. 运行uploadArchives的task,结果:

二、ASM工具使用
1. 如何查看字节码
-
Android Studio有一个插件——ASM Bytecode Outline,可以用来看类的字节码。
(未运行成功,可能跟kotlin有关...)
-
使用ASM提供的TraceClassVisitor,将类的字节码保存到本地,然后再分析。
2. 使用ASM提供的API进行方法修改
-
未添加代码的方法:
package com.example.autolog; public class Test { void test(){ } } -
使用TraceClassVisitor将对应的字节码保存到对应的文件,如下所示:
// class version 51.0 (51) // access flags 0x21 public class com/example/autolog/Test { // compiled from: Test.java // access flags 0x1 public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this Lcom/example/autolog/Test; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x0 test()V L0 LINENUMBER 6 L0 RETURN L1 LOCALVARIABLE this Lcom/example/autolog/Test; L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 } -
添加代码的方法:
package com.example.autolog; public class Test { void test(){ } } -
使用TraceClassVisitor将对应的字节码保存到对应的文件,如下所示:
// class version 51.0 (51) // access flags 0x21 public class com/example/autolog/Test { // compiled from: Test.java // access flags 0x1 public <init>()V L0 LINENUMBER 5 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this Lcom/example/autolog/Test; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x0 test()V L0 LINENUMBER 8 L0 INVOKESTATIC java/lang/System.currentTimeMillis ()J LSTORE 1 L1 LINENUMBER 9 L1 LDC "Test" NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V LDC "test start:" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LLOAD 1 INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; INVOKESTATIC android/util/Log.v (Ljava/lang/String;Ljava/lang/String;)I POP L2 LINENUMBER 10 L2 RETURN L3 LOCALVARIABLE this Lcom/example/autolog/Test; L0 L3 0 LOCALVARIABLE start J L1 L3 1 MAXSTACK = 4 MAXLOCALS = 3 } -
将字节码中test()中多出来的内容用ASM的api添加即可,在LogMethodVisitor类的visitCode()方法中