# asm-samples **Repository Path**: mayundaze/asm-samples ## Basic Information - **Project Name**: asm-samples - **Description**: Android 字节码插桩实战 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-11 - **Last Updated**: 2026-02-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # asm-samples Android 字节码插桩实战,相关文章: - [ASM 字节码插桩:实现双击防抖](https://juejin.cn/post/7042328862872567838) - [ASM 字节码插桩:进行线程整治](https://juejin.cn/post/7044339202997092383) - [ASM 字节码插桩:助力隐私合规](https://juejin.cn/post/7046207125785149448) - [ASM 字节码插桩:监控大图加载](https://juejin.cn/post/7074970389188706318) - [ASM 字节码插桩:从 Lambda 表达式讲起](https://juejin.cn/post/7151798531672506398) - [ASM 字节码插桩:Jetpack Compose 实现双击防抖](https://juejin.cn/post/7158061389503250445) 以上属于我的个人 Demo,无法直接应用到线上项目中。如果你需要的是开箱即用的字节码插桩库,可以用我另外一个开源库 - 一个 Android 字节码插桩库:[Track](https://github.com/leavesCZY/Track) ---------------------------- #### 1. ComposeOnClick 点击插桩的实现 **定位与引用** - ComposeOnClick 类位置与实现: - [ComposeOnClick.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt#L11-L29):实现 Function0 包装器,节流点击(500ms 内只触发一次),在 invoke 中判断时间窗口并转发调用。 - 全部引用位置: - [ComposeOnClick.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt) - [app/build.gradle.kts](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24):插件参数中将 onClickClass 配置为 ComposeOnClick,并设置 whiteListTag。 **插件代码(buildSrc/plugins/click/compose)** - 插件入口与注册: - [ComposeClickPlugin.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickPlugin.kt#L16-L33):注册变体级 ASM 变换,作用域 ALL,并设置帧计算模式;从 Gradle 扩展读取参数并注入到变换参数。 - 插件 ID 指向: [github.leavesczy.trace.click.compose.properties](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/build/resources/main/META-INF/gradle-plugins/github.leavesczy.trace.click.compose.properties#L1-L1) → implementation-class=ComposeClickPlugin。 - 参数类与配置对象: - [ComposeClickConfig.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickConfig.kt#L10-L26):ComposeClickConfig 序列化到 ASM 参数;将 pluginParameter.onClickClass 从点号转为斜杠路径。 - [ComposeClickConfig.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickConfig.kt#L28-L31):ComposeClickPluginParameter,包含 onClickClass、whiteListTag。 - 变换工厂与 ASM 使用: - [ComposeClickClassVisitorFactory.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L28-L51):定义 InstrumentationParameters,创建 ClassVisitor。 - [ComposeClickClassVisitorFactory.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L53-L127):核心改写逻辑在 ComposeClickClassVisitor.visitEnd。 **ASM 改写逻辑** - 目标类与方法签名: - 仅在 className 为 androidx.compose.foundation.ClickableKt 时生效([L61-L74](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L61-L74))。 - 匹配两类方法签名: - clickable 的 onClick 为第 7 个参数(index 6):[ClickableMethodDesc](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L63-L65)。 - combinedClickable 的 primary onClick 为第 10 个参数(index 9):[CombinedClickableMethodDesc](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L66-L68)。 - 插入指令序列(方法进入处插入): - 取第 5 个参数 label(index 4),与白名单标签 whiteListTag 比较 equals([L91-L106](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L91-L106))。 - 不相等则新建 onClickClass 实例并用原始 Function0 作为构造参数,替换对应的 onClick 实参([L106-L121](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L106-L121))。 - 这相当于在 compose clickable/combinedClickable 的 onClick 参数外层包一层 ComposeOnClick,从而实现全局节流。 **onClickClass 与 whiteListTag 用法** - 配置来源: - [app/build.gradle.kts](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24):onClickClass = "github.leavesczy.trace.click.compose.ComposeOnClick",whiteListTag = "noCheck"。 - 在变换中的作用: - onClickClass:作为 ASM NEW/INVOKESPECIAL 的 owner,用于构造包装器类;在配置阶段将包名转为 JVM 路径(点→斜杠),便于 ASM 使用([ComposeClickConfig invoke](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickConfig.kt#L17-L22))。 - whiteListTag:当 compose 的 clickable/combinedClickable 的 label 参数等于该值时,跳过包装;否则将 onClick 包装为 onClickClass 实例([比较与跳转](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose/ComposeClickClassVisitorFactory.kt#L93-L106))。 - 效果总结: - 默认对所有 clickable/combinedClickable 的 onClick 进行统一节流包装。 - 通过为 label 传入 whiteListTag(例如 "noCheck")实现白名单跳过,不进行包装。 **补充说明** - 该改写基于 AGP Instrumentation API(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS),不修改源码,仅在字节码层面替换 compose runtime 中 ClickableKt 的方法入参。 - 所有相关源码与配置已在上述位置列出,未发现其他引用或分支实现。 **整体原理** - ComposeOnClick 是一个函数包装器,实现 Function0,内部做“快速点击节流”:默认 500ms 窗口内只放行一次点击,超过窗口才转发真正的 onClick。 - 它并不是手动在每处调用时写出来的,而是由一个 Gradle 插件在编译期用 ASM 动态把 Compose 的 clickable/combinedClickable 的 onClick 参数替换成 ComposeOnClick 包装,从而做到全局统一防抖。 **源码与配置** - 包装器类:[ComposeOnClick.kt:L11](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt#L11-L11) - 类定义:class ComposeOnClick(private val onClick: () -> Unit) : Function0 - invoke 中先判断当前时间与上次点击时间;小于阈值直接返回,大于则执行 onClick - 插件参数声明(app 模块): - [app/build.gradle.kts:L21-L24](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24) - onClickClass = "github.leavesczy.trace.click.compose.ComposeOnClick" 用于告诉插件包装器的实现类 - whiteListTag = "noCheck" 用于白名单标签(见下文) **编译期插桩如何生效** - 插件入口注册了一个类访问器工厂,面向所有变体应用 ASM 插桩(在 buildSrc 中): - 插件入口与工厂:ComposeClickPlugin、ComposeClickClassVisitorFactory(在 buildSrc/plugins/click/compose) - 匹配目标 - 仅对 androidx.compose.foundation.ClickableKt 中的两个扩展函数做处理: - clickable(… onClick: Function0 …) — onClick 实参在第 7 个参数位(index 6) - combinedClickable(… onClick: Function0 …) — onClick 实参在第 10 个参数位(index 9) - 插桩逻辑 - 读取 Compose clickable/combinedClickable 的 label 参数(方法第 5 个形参,index 4) - 与 whiteListTag 比较: - 相等:跳过,不做包装 - 不相等:构造 onClickClass(即 ComposeOnClick)的实例,把原始 onClick 作为构造参数传入,然后用新实例替换原本的 onClick 实参 - 结果 - 所有未被白名单标签标记的点击处理都会被自动包上一层 ComposeOnClick,从而实现统一节流 - 若某个点击需要跳过节流,在调用 clickable/combinedClickable 时传 label = "noCheck" **你选中两处代码之间的关系** - [ComposeOnClick.kt 的类定义](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt#L11-L11):实现点击包装与节流的实际逻辑 - [app/build.gradle.kts 的插件参数](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24):告诉编译期插件“要用哪个包装器类”和“白名单标签是什么”。插件据此在字节码层面把 Compose 的 onClick 参数替换为 ComposeOnClick(除非 label 命中白名单) **使用建议** - 默认全局节流:无需改业务代码 - 临时跳过:在某个 clickable/combinedClickable 调用里传 label = "noCheck"(与 whiteListTag 一致) - 可调参数:需要更改节流时长或行为时,修改 ComposeOnClick 内部逻辑即可全局生效 #### 2. 点击事件插桩 - 项目中的集成 **集成思路** - 通过 Gradle 插件在编译期用 ASM 改写字节码,把 Compose 的 clickable/combinedClickable 的 onClick 参数替换为包装器类 ComposeOnClick,从而实现全局点击节流。 - 包装器类必须存在于运行时 classpath,插件需要知道它的全限定名;白名单标签可在业务里通过 label 参数跳过包装。 **在本仓库内的接入现状** - 包装器类已提供:[ComposeOnClick.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt)(实现 Function0,500ms 节流)。 - 插件已内置于 buildSrc,插件 id:github.leavesczy.trace.click.compose;在 app 中已配置参数:[app/build.gradle.kts:L21-L24](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24)。 - click 模块已启用 compose,且已按 Kotlin 2.x 要求应用 Compose 编译插件;非 compose 模块不会应用该插件,避免冗余依赖报错。 **在你的项目中集成的标准步骤** - 准备包装器类 - 将以下类放入一个所有使用 Compose 的模块都能依赖到的源码包(包名可自定义,但要与后面 onClickClass 保持一致)。 ```kotlin package your.pkg.path class ComposeOnClick(private val onClick: () -> Unit) : Function0 { private var lastClickTime = 0L private val intervalMs = 500L override fun invoke() { val now = System.currentTimeMillis() if (now - lastClickTime >= intervalMs) { lastClickTime = now onClick() } } } ``` - 引入字节码插桩插件 - 方式 A:直接复制本仓库的 buildSrc 插件到你的项目 - 复制目录:buildSrc/src/main/kotlin/github/leavesczy/trace/plugins/click/compose(以及其依赖的配置类/工厂类)。 - 在你的 buildSrc/build.gradle.kts 增加依赖(版本按你项目统一): - com.android.tools.build:gradle:${agpVersion} - org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion} - 对于 Kotlin 2.x:org.jetbrains.kotlin:compose-compiler-gradle-plugin:${kotlinVersion} - 在 buildSrc/gradle-plugin 元数据里声明插件 id 指向实现类(与本仓库一致即可)。 - 方式 B:将该插件发布到私服/本地 maven,再在项目里以二方包方式应用(流程略)。 - 在使用 Compose 的模块启用插件与 Compose - 模块级 build.gradle.kts: - 启用 Compose ```kotlin android { buildFeatures { compose = true } // Kotlin 1.9.x 可继续使用 composeOptions // composeOptions { kotlinCompilerExtensionVersion = "..." } } ``` - Kotlin 2.x 必须应用 Compose 编译插件(仅对使用 Compose 的模块) ```kotlin plugins { id("org.jetbrains.kotlin.plugin.compose") } ``` - 应用点击插桩插件(对使用 Compose 的模块) ```kotlin plugins { id("github.leavesczy.trace.click.compose") } ``` - 配置插件参数(告诉插件包装器类与白名单标签) ```kotlin extensions.configure { onClickClass = "your.pkg.path.ComposeOnClick" whiteListTag = "noCheck" } ``` - 业务侧使用与验证 - 默认所有 clickable/combinedClickable 的 onClick 都会被包装节流。 - 需要跳过某次点击节流时,传入 label = "noCheck"(与上面的 whiteListTag 保持一致): ```kotlin Modifier.clickable(label = "noCheck") { /* 不节流 */ } // 或默认节流: Modifier.clickable { /* 节流保护 */ } ``` - 混淆与发布注意 - 若开启 R8,请添加 keep,防止包装器类被移除或合成折叠: ``` -keep class your.pkg.path.ComposeOnClick { *; } ``` **版本与兼容性要点** - Kotlin 2.x:必须为启用 compose 的模块应用 org.jetbrains.kotlin.plugin.compose 插件;不使用 Compose 的模块不要应用它。 - Compose 版本升级可能变动 ClickableKt 方法签名;本插件当前针对 foundation ClickableKt 的 clickable/combinedClickable 两个入口和固定参数索引,如升级报方法匹配失败,需要同步更新 ASM 匹配描述符。 **快速自检清单** - 包装器类在运行时可见:模块有依赖到该类 - 使用 Compose 的模块: - 已开启 buildFeatures.compose - Kotlin 2.x 已应用 org.jetbrains.kotlin.plugin.compose - 已应用 github.leavesczy.trace.click.compose 且配置了 onClickClass/whiteListTag - 运行:快速双击验证仅触发一次;label 为 "noCheck" 时不节流 #### 3. buildSrc 模块的作用 问题是:buildSrc 为什么不在 settings.gradle.kts 里出现,它是怎么“被依赖”的? **核心结论** - buildSrc 是 Gradle 的“约定目录”。只要工程根目录下存在 buildSrc,Gradle 会在构建前先编译它,并把它的产物自动放到所有构建脚本的 classpath 上。 - 因为是约定生效,不需要、也不应该在 settings.gradle.kts 里 include buildSrc。 **它是如何被加载的** - 构建顺序:Gradle 启动 → 先编译 buildSrc → 把 buildSrc 的 classes/jars 加入 buildscript classpath → 再评估根工程和各子工程的脚本。 - 暴露能力: - 普通 Kotlin/Java 工具类可在脚本中直接 import 使用。 - 如果在 buildSrc 中应用了 java-gradle-plugin 并在 gradlePlugin {} 注册了插件,那么这些插件可以像外部插件一样用 id 应用。 **本仓库是怎么用的** - 在 buildSrc 里注册了多个自定义插件(包括 Compose 点击插桩): - 定义位置与 ID:[build.gradle.kts:L12-L35](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/build.gradle.kts#L12-L35) - 例如点击插桩插件的 ID 是 github.leavesczy.trace.click.compose,对应实现类在 buildSrc 下的 plugins 包里。 - 根工程把自定义“总控”插件应用到了所有子模块: - [build.gradle.kts](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/build.gradle.kts) 里有 subprojects { apply { plugin() } },这个 TracePlugin 也来自 buildSrc。 - TracePlugin 内部再为各模块按需应用 Android/Kotlin/Compose/点击插桩等插件与通用配置:[TracePlugin.kt](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/buildSrc/src/main/kotlin/github/leavesczy/trace/TracePlugin.kt) **和 ComposeOnClick 的关系** - 点击插桩插件(在 buildSrc 注册)会在编译期用 ASM 把 Compose clickable/combinedClickable 的 onClick 参数替换为你提供的包装类(onClickClass)。 - onClickClass 在 app 模块里通过扩展参数配置为 ComposeOnClick:[app/build.gradle.kts:L21-L24](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/app/build.gradle.kts#L21-L24) - 包装类实现在业务模块可见处:[ComposeOnClick.kt:L11](file:///Users/wb-zhaoyaze/Home/Project/AndroidProject/asm-samples/click/src/main/java/github/leavesczy/trace/click/compose/ComposeOnClick.kt#L11-L11) **常见问题** - settings.gradle.kts 不需要 include buildSrc;这是 Gradle 的隐式机制。 - 修改了 buildSrc 的代码后,Gradle 会自动检测变更并先重编译 buildSrc;若 buildSrc 编译失败,整个构建会失败。 - 如果你在 Kotlin 2.x 环境使用 Compose,确保在使用 Compose 的模块(例如 click)应用 Compose 编译插件;本仓库已通过 buildSrc 的逻辑处理好了这一点。