文章目录一、ASM简介1. 设计框架2. 设计模式访问者模式和责任链模式3. visitor访问顺序二、ASM插桩常见用途1. 性能监控优化2. 自动化埋点与数据采集无痕埋点3. 热修复与功能动态化4. 隐私合规与安全改造三、ASM实现函数耗时统计1. AGP环境2. 插件类3. 生成ClassVisitor的工厂类4. 函数插桩实现。5. 插桩实现的效果四、常用的工具类五、ASM插桩经典架构1. 经典架构2. 优化ClassReader读取效率3. 优化原理六、类结构七、参考资料原文链接 https://blog.csdn.net/followYouself/article/details/160512010一、ASM简介1. 设计框架说明功能定位核心职责关键函数ClassReader数据解析器负责解析原始类的字节数组并将其结构化的事件流传递给访问者对象驱动整个流程。构造函数ClassReader( byte[] classFile)接收Visitorvoid accept(ClassVisitor classVisitor, int parsingOptions)ClassVisitor访问者接口/抽象类定义了类各个结构如字段、方法、注解的访问方法是修改字节码的入口。构造函数ClassVisitor(int api, ClassVisitor classVisitor)方法visit、visitOuterClass、visitAnnotation、visitField、visitMethodClassWriter字节码生成器继承自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/1604498052. 插件类packagecom.example.asm.testimportcom.android.build.api.instrumentation.FramesComputationModeimportcom.android.build.api.instrumentation.InstrumentationScopeimportcom.android.build.api.variant.AndroidComponentsExtensionimportorg.gradle.api.Pluginimportorg.gradle.api.ProjectclassAsmPlugin:PluginProject{overridefunapply(project:Project){project.logger.lifecycle( ASM Method Time Cost Plugin Applied )LogUtil.init(project.logger)valandroidComponentsproject.extensions.getByType(AndroidComponentsExtension::class.java)androidComponents.onVariants{variant-project.logger.quiet(注册 ASM 变换到 variant:${variant.name})// 注册 AsmClassVisitorFactoryvariant.instrumentation.transformClassesWith(AsmClassVisitorFactoryImpl::class.java,InstrumentationScope.PROJECT){// 配置参数如果需要}// 设置 ASM frames 计算模式对应ASM中的ClassWriter.COMPUTE_FRAMESvariant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)}}}3. 生成ClassVisitor的工厂类packagecom.example.asm.testimportcom.android.build.api.instrumentation.AsmClassVisitorFactoryimportcom.android.build.api.instrumentation.ClassContextimportcom.android.build.api.instrumentation.ClassDataimportcom.android.build.api.instrumentation.InstrumentationParametersimportorg.objectweb.asm.ClassVisitorimportorg.objectweb.asm.util.TraceClassVisitorimportorg.objectweb.asm.util.CheckClassAdapterimportjava.io.PrintWriterabstractclassAsmClassVisitorFactoryImpl:AsmClassVisitorFactoryInstrumentationParameters.None{overridefuncreateClassVisitor(classContext:ClassContext,nextClassVisitor:ClassVisitor):ClassVisitor{// 责任链模式valcheckClassVisitorCheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范如果不符合规范会抛出异常valtraceClassVisitorTraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcvMethodTimeCostClassVisitor(traceClassVisitor)returncv}// 判断是否需要对该类进行插桩对不需要插桩的类进行过滤overridefunisInstrumentable(classData:ClassData):Boolean{returnclassData.className.contains(com.example.myapplication2.ui)}}4. 函数插桩实现。MethodTimeCostMethodVisitor继承自AdviceAdapter类重写onMethodEnter和onMethodExit放在在函数进入退出时插桩。packagecom.example.asm.testimportorg.gradle.api.logging.Loggerimportorg.objectweb.asm.ClassVisitorimportorg.objectweb.asm.ClassWriterimportorg.objectweb.asm.MethodVisitorimportorg.objectweb.asm.Opcodesimportorg.objectweb.asm.Typeimportorg.objectweb.asm.commons.AdviceAdapterclassMethodTimeCostClassVisitor(classVisitor:ClassVisitor):ClassVisitor(Opcodes.ASM9,classVisitor){privatevallogger:Logger?LogUtil.getLogger()init{if(classVisitorisClassWriter){logger?.quiet(classVisitor is ClassWriter instance)}logger?.lifecycle(MethodTimeCostClassVisitor 初始化)}overridefunvisit(version:Int,access:Int,name:String?,signature:String?,superName:String?,interfaces:ArrayoutString??){logger?.lifecycle(MethodTimeCostClassVisitor visit method:$name)super.visit(version,access,name,signature,superName,interfaces)}overridefunvisitMethod(access:Int,name:String?,descriptor:String?,signature:String?,exceptions:ArrayoutString??):MethodVisitor?{valmvsuper.visitMethod(access,name,descriptor,signature,exceptions)returnif(mv!null)MethodTimeCostMethodVisitor(mv,access,name,descriptor)elsemv}privateclassMethodTimeCostMethodVisitor(mv:MethodVisitor,access:Int,name:String?,descriptor:String?,privatevallogger:Logger?null):AdviceAdapter(Opcodes.ASM9,mv,access,name,descriptor){// 用于存储开始时间的局部变量索引long 类型需要 2 个 slotprivatevartimeVarIndex-1overridefunonMethodEnter(){// 调用 System.currentTimeMillis() 记录开始时间mv.visitMethodInsn(INVOKESTATIC,java/lang/System,currentTimeMillis,()J,false)// 将返回的 long 时间存储到局部变量表中timeVarIndexnewLocal(Type.LONG_TYPE)storeLocal(timeVarIndex)logger?.debug(方法$name进入时已插入时间记录代码)}overridefunonMethodExit(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 转为 Stringmv.visitMethodInsn(INVOKESTATIC,java/lang/String,valueOf,(J)Ljava/lang/String;,false)// 3. 拼接字符串methodName cost: duration msmv.visitTypeInsn(NEW,java/lang/StringBuilder)mv.visitInsn(DUP)mv.visitMethodInsn(INVOKESPECIAL,java/lang/StringBuilder,init,()V,false)mv.visitLdcInsn(method$namecost: )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)// 拼接msmv.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插桩。不用考虑帧结构问题valcheckClassVisitorCheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范如果不符合规范会抛出异常valtraceClassVisitorTraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcvMethodTimeCostClassVisitor(traceClassVisitor)//责任链模式最外层的classVisitor先执行。插桩类在最外层五、ASM插桩经典架构1. 经典架构byte[]b1...;ClassWritercwnewClassWriter(0);// cv 将所有事件转发给 cwClassVisitorcvnewClassVisitor(ASM4,cw){// 修改类内容 };ClassReadercrnewClassReader(b1);cr.accept(cv,0);byte[]b2cw.toByteArray();// b2 与 b1 表示同一个类2. 优化ClassReader读取效率在构造ClassWriter时传入ClassReader对象如果方法没有修改过classReader遍历方法时就会直接将原方法拷贝而不详细解析方法体。实际上ClassReader是将symbolTable传入ClassWriter。用于直接解析方法相关的位置信息。byte[]b1...ClassReadercrnewClassReader(b1);ClassWritercwnewClassWriter(cr,0);// 优化点ClassVisitorcvnewClassVisitor(ASM4,cw){// 修改类内容 };cr.accept(ca,0);byte[]b2cw.toByteArray();3. 优化原理优化源码参考org.objectweb.asm.ClassReader#readMethod参考文档https://www.yuque.com/mikaelzero/asm/bwbaz7在ClassReader组件的accept方法参数中传送了ClassVisitor如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter这意味着这个方法的内容将不会被转换事实上应用程序甚至不会 看到其内容。 在这种情况下ClassReader组件不会分析这个方法的内容不会生成相应事件只是复制ClassWriter中表示这个方法的字节数组。// 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(methodVisitorinstanceofMethodWriter){MethodWritermethodWriter(MethodWriter)methodVisitor;if(methodWriter.canCopyMethodAttributes(this,synthetic,(context.currentMethodAccessFlagsOpcodes.ACC_DEPRECATED)!0,readUnsignedShort(methodInfoOffset4),signatureIndex,exceptionsOffset)){methodWriter.setMethodAttributesSource(methodInfoOffset,currentOffset-methodInfoOffset);returncurrentOffset;}}六、类结构类结构详细结构类信息修饰符、类名字、超类、接口常量池数值、字符串、类型常量其他源文件名、封装的类引用、注释*、属性*内部类*名称字段*修饰符、名字、类型、注释*、属性*方法*修饰符、名字、返回类型与参数类型、注释*、属性*、编译后的代码finalintaccess// 访问权限finalStringname// 函数名、字段名finalStringdescriptor// 字段的类型函数的描述符finalStringsignature// 泛型信息七、参考资料ASM版本implementation org.ow2.asm:asm:7.2文档https://www.yuque.com/mikaelzero/asm原文链接转载请附上原文出处链接和本声明https://blog.csdn.net/followYouself/article/details/160512010