文章目录
-
- 一、ASM简介
-
- [1. 设计框架](#1. 设计框架)
- [2. 设计模式:访问者模式和责任链模式](#2. 设计模式:访问者模式和责任链模式)
- [3. visitor访问顺序](#3. visitor访问顺序)
- 二、ASM插桩常见用途
-
- [1. 性能监控优化](#1. 性能监控优化)
- [2. 自动化埋点与数据采集(无痕埋点)](#2. 自动化埋点与数据采集(无痕埋点))
- [3. 热修复与功能动态化](#3. 热修复与功能动态化)
- [4. 隐私合规与安全改造](#4. 隐私合规与安全改造)
- 三、ASM实现函数耗时统计
-
- [1. AGP环境](#1. AGP环境)
- [2. 插件类](#2. 插件类)
- [3. 生成ClassVisitor的工厂类](#3. 生成ClassVisitor的工厂类)
- [4. 函数插桩实现。](#4. 函数插桩实现。)
- [5. 插桩实现的效果](#5. 插桩实现的效果)
- 四、常用的工具类
- 五、ASM插桩经典架构
-
- [1. 经典架构](#1. 经典架构)
- [2. 优化ClassReader读取效率](#2. 优化ClassReader读取效率)
- [3. 优化原理](#3. 优化原理)
- 六、类结构
- 七、参考资料
一、ASM简介
1. 设计框架

| 说明 | 功能定位 | 核心职责 | 关键函数 |
|---|---|---|---|
| ClassReader | 数据解析器 | 负责解析原始类的字节数组,并将其结构化的事件流传递给访问者对象,驱动整个流程。 | 构造函数:ClassReader( byte[] classFile) 接收Visitor:void accept(ClassVisitor classVisitor, int parsingOptions) |
| ClassVisitor | 访问者接口/抽象类 | 定义了类各个结构(如字段、方法、注解)的访问方法,是修改字节码的入口。 | 构造函数:ClassVisitor(int api, ClassVisitor classVisitor) 方法:visit、visitOuterClass、visitAnnotation、visitField、visitMethod |
| ClassWriter | 字节码生成器 | 继承自ClassVisitor,负责接收访问事件并生成修改后的二进制字节数组。 | 构造函数:ClassWriter(ClassReader classReader, int flags) 生成类:byte[] toByteArray() |
2. 设计模式:访问者模式和责任链模式
- 访问者接口 (Visitor): 定义了访问每一个具体元素的方法
visit(Element)。 - 具体访问者 (Concrete Visitor): 实现访问者接口,负责定义具体的算法/操作逻辑。
- 元素接口 (Element): 定义一个
accept(Visitor)方法,允许访问者访问。 - 具体元素 (Concrete Element): 实现
accept方法,并在该方法内部回调访问者的visit方法。 - 对象结构 (Object Structure): 用于存储和遍历元素对象集合(如列表或树)。
3. visitor访问顺序
- 类:
visit visitSource? visitOuterClass? (visitAnnotation|visitAttribute)* (visitInnerClass|visitField|visitMethod)* visitEnd - 函数方法:
visitAnnotationDefault? (visitAnnotation|visitParameterAnnotation|visitAttribute)* (visitCode (visitTryCatchBlock|visitLabel|visitFrame|visitXxxInsn|visitLocalVariable|visitLineNumber)* visitMaxs)? visitEnd
二、ASM插桩常见用途
1. 性能监控优化
- 批量统计函数执行耗时,自动在方法开头插入
System.currentTimeMillis(),结尾插入计算与上报逻辑,用于定位启动慢、卡顿的方法。比如BlockCanary。 - 批量trace插桩。启动速度优化 :在
Application、Activity关键生命周期方法中插入 Trace 开关,精准统计冷启动、温启动各阶段耗时。
2. 自动化埋点与数据采集(无痕埋点)
- 全量页面访问统计 :拦截
Activity.onCreate/onResume、Fragment.onResume,自动上报页面名称、停留时长。 - 点击事件埋点 :在
View.OnClickListener.onClick执行前插入代码,获取控件 ID、文本、位置等信息进行上报。 - 列表曝光统计 :结合
RecyclerView的onBindViewHolder或滚动监听,插入曝光标记代码。
3. 热修复与功能动态化
热修复框架的核心机制之一就是通过字节码插桩为每个方法预留"补丁"入口。
- 方法替换(Method Hook) :在每个方法开头插入一个静态方法调用,检查是否有需要执行的补丁代码,如有则跳转执行补丁,实现不重启修复线上 bug。代表框架:
Tinker、Sophix。 - 资源修复/So 修复:同样可在初始化阶段插入代码,实现资源路径或 So 加载路径的替换。
4. 隐私合规与安全改造
- 敏感 API 统一拦截/替换 :扫描所有调用
TelephonyManager.getDeviceId()、Settings.Secure.getString()(获取 Android ID)、MAC地址获取等代码行,替换为返回"合规空值"或统一管理,以适应监管要求。 - 增加try catch安全防护:对一些通用逻辑增加catch保护,减少线上崩溃。
三、ASM实现函数耗时统计
1. AGP环境
-
AGP 7.x以后,支持使用
AsmClassVisitorFactory实现ASM字节码插桩,废弃掉传统的transform API接口。 -
Gradle插件实现参考:https://blog.csdn.net/followYouself/article/details/160449805
2. 插件类
kotlin
package com.example.asm.test
import com.android.build.api.instrumentation.FramesComputationMode
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class AsmPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.logger.lifecycle("=========== ASM Method Time Cost Plugin Applied ===============")
LogUtil.init(project.logger)
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
project.logger.quiet("注册 ASM 变换到 variant: ${variant.name}")
// 注册 AsmClassVisitorFactory
variant.instrumentation.transformClassesWith(
AsmClassVisitorFactoryImpl::class.java,
InstrumentationScope.PROJECT
) {
// 配置参数(如果需要)
}
// 设置 ASM frames 计算模式,对应ASM中的ClassWriter.COMPUTE_FRAMES
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
)
}
}
}
3. 生成ClassVisitor的工厂类
kotlin
package com.example.asm.test
import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.util.TraceClassVisitor
import org.objectweb.asm.util.CheckClassAdapter
import java.io.PrintWriter
abstract class AsmClassVisitorFactoryImpl : AsmClassVisitorFactory<InstrumentationParameters.None> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
// 责任链模式
val checkClassVisitor = CheckClassAdapter(nextClassVisitor) // 检查asm修改后的代码是否符合规范,如果不符合规范,会抛出异常
val traceClassVisitor = TraceClassVisitor(checkClassVisitor, PrintWriter(System.out)) // 打印asm修改后的代码
val cv = MethodTimeCostClassVisitor(traceClassVisitor)
return cv
}
// 判断是否需要对该类进行插桩,对不需要插桩的类进行过滤
override fun isInstrumentable(classData: ClassData): Boolean {
return classData.className.contains("com.example.myapplication2.ui")
}
}
4. 函数插桩实现。
MethodTimeCostMethodVisitor继承自AdviceAdapter类,重写onMethodEnter和onMethodExit放在在函数进入退出时插桩。
kotlin
package com.example.asm.test
import org.gradle.api.logging.Logger
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.AdviceAdapter
class MethodTimeCostClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, classVisitor) {
private val logger: Logger? = LogUtil.getLogger()
init {
if (classVisitor is ClassWriter) {
logger?.quiet("classVisitor is ClassWriter instance")
}
logger?.lifecycle("MethodTimeCostClassVisitor 初始化")
}
override fun visit(
version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String?>?
) {
logger?.lifecycle("MethodTimeCostClassVisitor visit method: $name")
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array<out String?>?
): MethodVisitor? {
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
return if (mv != null) MethodTimeCostMethodVisitor(mv, access, name, descriptor) else mv
}
private class MethodTimeCostMethodVisitor(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
private val logger: Logger? = null
) : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
// 用于存储开始时间的局部变量索引(long 类型需要 2 个 slot)
private var timeVarIndex = -1
override fun onMethodEnter() {
// 调用 System.currentTimeMillis() 记录开始时间
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
// 将返回的 long 时间存储到局部变量表中
timeVarIndex = newLocal(Type.LONG_TYPE)
storeLocal(timeVarIndex)
logger?.debug("方法 $name 进入时已插入时间记录代码")
}
override fun onMethodExit(opcode: Int) {
if (timeVarIndex == -1) return
// 1. 获取结束时间并计算耗时
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
loadLocal(timeVarIndex)
mv.visitInsn(LSUB)
// 2. 使用 String.valueOf() 将 long 转为 String
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(J)Ljava/lang/String;", false)
// 3. 拼接字符串:"methodName cost: " + duration + " ms"
mv.visitTypeInsn(NEW, "java/lang/StringBuilder")
mv.visitInsn(DUP)
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
mv.visitLdcInsn("method $name cost: ")
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitInsn(SWAP) // 交换 StringBuilder 和 duration 字符串
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) // 拼接耗时
mv.visitLdcInsn(" ms") // 拼接ms
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
// 4. 调用 Log.i()
mv.visitLdcInsn("$name")
mv.visitInsn(SWAP)
mv.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false)
mv.visitInsn(POP)
logger?.debug("方法 $name 退出时已插入耗时计算代码")
}
}
}
5. 插桩实现的效果

四、常用的工具类
| 类名 | 作用 |
|---|---|
| TraceClassVisitor | 打印转换完成后的字节码 |
| CheckClassAdapter | 校验字节码文件是否合法,字节码文件不合法时抛出编译异常。 |
| AdviceAdapter | 有函数进入和退出的回调,用于实现ASM插桩。不用考虑帧结构问题 |
kotlin
val checkClassVisitor = CheckClassAdapter(nextClassVisitor) // 检查asm修改后的代码是否符合规范,如果不符合规范,会抛出异常
val traceClassVisitor = TraceClassVisitor(checkClassVisitor, PrintWriter(System.out)) // 打印asm修改后的代码
val cv = MethodTimeCostClassVisitor(traceClassVisitor) //责任链模式,最外层的classVisitor先执行。插桩类在最外层
五、ASM插桩经典架构
1. 经典架构
java
byte[] b1 = ...;
ClassWriter cw = new ClassWriter(0);
// cv 将所有事件转发给 cw
ClassVisitor cv = new ClassVisitor(ASM4, cw) { // 修改类内容 };
ClassReader cr = new ClassReader(b1);
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray(); // b2 与 b1 表示同一个类
2. 优化ClassReader读取效率
在构造ClassWriter时,传入ClassReader对象,如果方法没有修改过,classReader遍历方法时,就会直接将原方法拷贝,而不详细解析方法体。- 实际上ClassReader是将
symbolTable传入ClassWriter。用于直接解析方法相关的位置信息。
java
byte[] b1 = ...
ClassReader cr = new ClassReader(b1);
ClassWriter cw = new ClassWriter(cr, 0); // 优化点
ClassVisitor cv = new ClassVisitor(ASM4, cw) { // 修改类内容 };
cr.accept(ca, 0);
byte[] b2 = cw.toByteArray();
3. 优化原理
- 优化源码参考:org.objectweb.asm.ClassReader#readMethod,参考文档:https://www.yuque.com/mikaelzero/asm/bwbaz7
在ClassReader组件的accept方法参数中传送了ClassVisitor,如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter,这意味着这个方法的内容将不会被转换,事实上,应用程序甚至不会 看到其内容。 在这种情况下,ClassReader组件不会分析这个方法的内容,不会生成相应事件,只是复制ClassWriter中表示这个方法的字节数组。
java
// If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
// adapter between the reader and the writer. In this case, it might be possible to copy
// the method attributes directly into the writer. If so, return early without visiting
// the content of these attributes.
if (methodVisitor instanceof MethodWriter) {
MethodWriter methodWriter = (MethodWriter) methodVisitor;
if (methodWriter.canCopyMethodAttributes(
this,
synthetic,
(context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
readUnsignedShort(methodInfoOffset + 4),
signatureIndex,
exceptionsOffset)) {
methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset);
return currentOffset;
}
}
六、类结构
| 类结构 | 详细结构 |
|---|---|
| 类信息 | 修饰符、类名字、超类、接口 |
| 常量池 | 数值、字符串、类型常量 |
| 其他 | 源文件名、封装的类引用、注释*、属性* |
| 内部类* | 名称 |
| 字段* | 修饰符、名字、类型、注释*、属性* |
| 方法* | 修饰符、名字、返回类型与参数类型、注释*、属性*、编译后的代码 |
java
final int access // 访问权限
final String name // 函数名、字段名
final String descriptor // 字段的类型,函数的描述符
final String signature // 泛型信息
七、参考资料
- ASM版本
implementation "org.ow2.asm:asm:7.2" - 文档:https://www.yuque.com/mikaelzero/asm