字节码插桩
源:Android Gradle Plugin API | ASM
在编译期修改字节码,实现无痕埋点、性能监控等高级功能。
字节码插桩概念
什么是字节码插桩
字节码插桩(Bytecode Instrumentation):在 .class 文件生成后、打包进 APK 前,动态修改字节码。
执行时机:
Kotlin/Java 源码
↓ 编译
.class 文件
↓ 插桩 ← 这里!
修改后的 .class
↓ 打包
DEX / APK使用场景
无痕埋点:
- 自动记录所有点击事件
- 记录页面访问
- 网络请求监控
性能监控:
- 方法耗时统计
- 内存分配追踪
- 线程监控
修复 Bug:
- 修改第三方库行为
- 热修复
- AOP 编程
ASM 基础
ASM 是什么
ASM:Java 字节码操作和分析框架。
核心 API:
ClassVisitor:访问类MethodVisitor:访问方法FieldVisitor:访问字段
字节码结构
简单示例:
kotlin
fun greet(name: String) {
println("Hello, $name")
}字节码:
public final greet(Ljava/lang/String;)V
L0
LDC "Hello, "
ALOAD 1
INVOKESTATIC kotlin/jvm/internal/Intrinsics.stringPlus(...)
INVOKEVIRTUAL java/io/PrintStream.println(...)
L1
RETURNTransform API(已废弃)
传统 Transform
AGP 7.0 之前:
kotlin
// ❌ 已废弃,不推荐
class MyTransform : Transform() {
override fun transform(transformInvocation: TransformInvocation) {
// 处理所有 class 文件
}
}问题:
- 性能差
- 不支持增量
- 难以并行
AsmClassVisitorFactory API
现代插桩方案
AGP 7.0+:使用 AsmClassVisitorFactory。
核心接口:
kotlin
interface AsmClassVisitorFactory<ParametersT : InstrumentationParameters> :
InstrumentationArtifact {
fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor
fun isInstrumentable(classData: ClassData): Boolean
}基本实现
kotlin
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.MethodVisitor
import org.objectweb.asm.Opcodes
interface MyParameters : InstrumentationParameters {
// 参数
}
abstract class MyClassVisitorFactory :
AsmClassVisitorFactory<MyParameters> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return MyClassVisitor(nextClassVisitor)
}
override fun isInstrumentable(classData: ClassData): Boolean {
// 过滤:只处理自己的代码
return classData.className.startsWith("com.example.myapp")
}
}
class MyClassVisitor(nextVisitor: ClassVisitor) :
ClassVisitor(Opcodes.ASM9, nextVisitor) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor {
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
return MyMethodVisitor(mv, name)
}
}
class MyMethodVisitor(
nextVisitor: MethodVisitor,
private val methodName: String
) : MethodVisitor(Opcodes.ASM9, nextVisitor) {
override fun visitCode() {
super.visitCode()
// 在方法开始插入代码
mv.visitLdcInsn(methodName)
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/example/Monitor",
"onMethodEnter",
"(Ljava/lang/String;)V",
false
)
}
}注册插桩
build.gradle.kts:
kotlin
androidComponents {
onVariants { variant ->
variant.instrumentation.transformClassesWith(
MyClassVisitorFactory::class.java,
InstrumentationScope.ALL
) {
// 配置参数
}
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COPY_FRAMES
)
}
}实战案例
案例1:方法耗时统计
kotlin
class TimingClassVisitor(nextVisitor: ClassVisitor) :
ClassVisitor(Opcodes.ASM9, nextVisitor) {
private var className = ""
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>?
) {
className = name
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor {
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
return TimingMethodVisitor(mv, className, name)
}
}
class TimingMethodVisitor(
nextVisitor: MethodVisitor,
private val className: String,
private val methodName: String
) : MethodVisitor(Opcodes.ASM9, nextVisitor) {
override fun visitCode() {
super.visitCode()
// long startTime = System.currentTimeMillis();
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false
)
mv.visitVarInsn(Opcodes.LSTORE, getStartTimeSlot())
}
override fun visitInsn(opcode: Int) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
// 在 return 前插入
// long endTime = System.currentTimeMillis();
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"java/lang/System",
"currentTimeMillis",
"()J",
false
)
// Monitor.record(className, methodName, endTime - startTime);
mv.visitVarInsn(Opcodes.LLOAD, getStartTimeSlot())
mv.visitInsn(Opcodes.LSUB)
mv.visitLdcInsn(className)
mv.visitLdcInsn(methodName)
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/example/Monitor",
"record",
"(JLjava/lang/String;Ljava/lang/String;)V",
false
)
}
super.visitInsn(opcode)
}
private fun getStartTimeSlot() = /* ... */
}Monitor.java:
java
public class Monitor {
public static void record(long duration, String className, String methodName) {
Log.d("Timing", className + "." + methodName + ": " + duration + "ms");
}
}案例2:点击事件埋点
kotlin
class ClickTrackingVisitor(nextVisitor: ClassVisitor) :
ClassVisitor(Opcodes.ASM9, nextVisitor) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor {
val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
// 处理 onClick 方法
if (name == "onClick" && descriptor == "(Landroid/view/View;)V") {
return ClickMethodVisitor(mv)
}
return mv
}
}
class ClickMethodVisitor(nextVisitor: MethodVisitor) :
MethodVisitor(Opcodes.ASM9, nextVisitor) {
override fun visitCode() {
super.visitCode()
// Tracker.trackClick(view);
mv.visitVarInsn(Opcodes.ALOAD, 1) // view 参数
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/example/Tracker",
"trackClick",
"(Landroid/view/View;)V",
false
)
}
}InstrumentationScope
作用域选择
kotlin
variant.instrumentation.transformClassesWith(
MyFactory::class.java,
InstrumentationScope.ALL // 所有代码
)
// 可选值:
// - InstrumentationScope.PROJECT:仅项目代码
// - InstrumentationScope.DEPENDENCIES:仅依赖
// - InstrumentationScope.ALL:所有最佳实践
性能优化:
kotlin
override fun isInstrumentable(classData: ClassData): Boolean {
// 尽早过滤,减少处理
return classData.className.startsWith("com.example.myapp")
}调试:
kotlin
// 使用 ASM Bytecode Viewer 插件查看字节码
// 先写出期望的 Java 代码,再查看对应字节码Frame 计算:
kotlin
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COPY_FRAMES // 推荐
)错误处理:
kotlin
override fun visitCode() {
try {
super.visitCode()
// 插桩逻辑
} catch (e: Exception) {
// 记录错误但不中断构建
logger.error("Instrumentation failed", e)
}
}编译开关:
kotlin
// 仅在 Debug 构建启用
if (variant.buildType == "debug") {
variant.instrumentation.transformClassesWith(...)
}