{"id":19561941,"url":"https://github.com/beansoft/asm-example","last_synced_at":"2025-10-27T08:52:01.626Z","repository":{"id":47679586,"uuid":"447190063","full_name":"beansoft/asm-example","owner":"beansoft","description":"A Android ASM example 隐私合规检测与处理","archived":false,"fork":false,"pushed_at":"2022-07-21T10:48:08.000Z","size":351,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-12T14:05:12.687Z","etag":null,"topics":["andriod","asm","gradle-plugin"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beansoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-12T11:23:53.000Z","updated_at":"2025-02-20T09:20:58.000Z","dependencies_parsed_at":"2022-08-22T12:21:18.582Z","dependency_job_id":null,"html_url":"https://github.com/beansoft/asm-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/beansoft/asm-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beansoft%2Fasm-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beansoft%2Fasm-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beansoft%2Fasm-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beansoft%2Fasm-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beansoft","download_url":"https://codeload.github.com/beansoft/asm-example/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beansoft%2Fasm-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281241872,"owners_count":26467373,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["andriod","asm","gradle-plugin"],"created_at":"2024-11-11T05:12:59.512Z","updated_at":"2025-10-27T08:52:01.582Z","avatar_url":"https://github.com/beansoft.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"## ⚽️ ASM 的简单案例使用\n\n关于 ASM 书中的示例代码，都汇集在这个仓库了 [asm-module](https://github.com/dengshiwei/asm-module)，欢迎关注 star ღ( ´･ᴗ･` )比心\n\n### ASM 字节码增强技术—给所有方法添加日志和异常捕捉\n\n在 [AOP 利器 ASM 基础入门](https://juejin.cn/post/6877925000045658119) 博文中，介绍了 `ASM` 的基础知识和整体 `API` 结构，让我们对 `ASM` 有一个初步的认识。本篇博文将介绍三个案例来加深对 `ASM` 接口的理解。\n\n1. 删除日志：删除项目中的所有 `Log` 输出\n\n2. 添加日志：给项目添加 `Log` 日志输出\n\n3. 添加 `try...catch` 异常捕捉：给项目的所有方法添加异常步骤\n\n文中的案例皆是以 `Android` 项目为基础，所以先介绍一些基本概念。\n\n#### 1. 基本概念\n\n##### 1.1 Transform API\n\n`Android Gradle` 在 `1.5.0` 版本后提供了 `Transfrom API` 接口，允许第三方 `Plugin` 在打包 `dex` 之前的编译过程中操作 `.class` 文件。通俗点说 `Android` 提供了在编译时修改字节码的入口。\n\n一个 `Transform` 就是一个新的 `Task`，它是通过链式进行执行，即上一个 `Transform` 的输出作为当前 `Transform` 的输入，它的输出又作为下一个 `Transform` 的输入。`Transform` 的输入是用 `TransformInput` 表示，包含 `JarInput` 和 `DirectoryInput`，输出使用 `TransformOutputProvider` 表示。\n\n![./transform.png](./doc/transform.jpeg)\n\n##### 1.2 自定义插件\n\n在 `Android` 中提供了很多插件，比如 `apply plugin: 'com.android.application'` ：表示一个 `App` 应用的插件；`apply plugin: 'com.android.library'`：表示一个类库的插件。同样我们可以继承 `Plugin` 实现一个自定义插件。在实际的业务中通常会采用 `Plugin` + `Transform` + `ASM` 的方式来实现一个功能强大的自定义插件。关于自定义插件的实现，有很多博文都有介绍，这里就不展开了。\n\n\u003e 涉及的案例源码下载地址：https://github.com/dengshiwei/asm-example\n\n#### 2. 删除日志\n\n在 `Android` 开发中，我们常会使用 `Log` 类进行日志的输出，但是一些安全检测会认为输出日志是一种风险行为，所以要求被检测的 `App` 删除所有的日志打印。那么我们就可以通过 `ASM` 的技术，在编译时期实现进行 `Log` 日志的删除。\n\n##### 目标\n\n删除项目中所有的 `Log` 日志类输出的日志。\n\n##### 思路\n\n既然要删除项目中所有的 `Log` 日志输出，那么我们就需要检测 `Log` 日志输出在哪里被调用。通过 [AOP 利器 ASM 基础入门](https://juejin.cn/post/6877925000045658119) 博文可以了解到，`MethodVisitor` 类用于方法的访问，其中 `visitMethodInsn` 接口是对方法实现的每个指令的回调。我们只需要在这个回调里判断是 `Log` 类的 `d`、`e` 或 `i` 等的日志输出方法时，然后直接返回即可。\n\n##### 关键代码实现\n\n```kotlin\n/**\n * 在方法内部调用的地方，检测 Log 的用法，然后进行删除\n */\noverride fun visitMethodInsn(opcodeAndSource: Int, owner: String?, name: String?, descriptor: String?, isInterface: Boolean) {\n    if (Opcodes.ACC_STATIC.and(opcodeAndSource) != 0 \u0026\u0026 owner == \"android/util/Log\" \u0026\u0026 (name == \"d\" || name == \"i\" || name == \"e\" || name == \"w\" || name == \"v\")\n            \u0026\u0026 descriptor == \"(Ljava/lang/String;Ljava/lang/String;)I\"\n    ) {\n        /**\n         * 直接 return 只是删除了 Log 指令的调用，但是对应的当前的操作数栈没有进行处理\n         */\n        return\n    }\n\n    super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)\n}\n```\n\n上面的代码中，在 `visitMethodInsn` 方法中判断 `owner` 类名是不是 `Log`、方法名 `name` 是 `d`、`i`、`e`、`w` 或 `v`，并且方法修饰符是 `static` 类型，如果满足上面的条件，则说明该处方法调用是 `Log` 日志输出，则直接 `return`。\n\n#### 3. 添加日志\n\n一个接口被多个地方调用，排查问题时就很需要打印出方法的调用顺序，开发的时候没打印这些信息，手动添加有可能有遗漏，所以自定义插件添加最方便。\n\n##### 目标\n\n在方法进入的时候，添加日志打印当前方法的名称。\n\n##### 思路\n\n同样，打印所有方法的调用名称，我们需要在方法进入的时候调用 `Log` 类进行日志的输出。通过 [AOP 利器 ASM 基础入门](https://juejin.cn/post/6877925000045658119) 博文可以了解到，`ASM` 中提供的 `AdviceAdapter` 类可以检测方法的访问时机。\n\n- `onMethodEnter`：方法访问开始时\n- `onMethodExit`：方法访问结束时\n\n我们只需要在 `onMethodEnter` 方法中调用 `Log` 进行日志输出即可。\n\n##### 关键代码实现\n\n```kotlin\n/**\n * 增加所有调用方法的名称日志输出的地方\n */\ninternal class PrintLogInterceptor(var className: String?, methodVisitor: MethodVisitor,\n                                   access: Int,\n                                   name: String?,\n                                   descriptor: String?) : AdviceAdapter(PluginConstant.ASM_VERSION, methodVisitor, access, name, descriptor) {\n\n    override fun onMethodEnter() {\n        super.onMethodEnter()\n        // 将当前类名添加到操作栈，作为 TAG\n        mv.visitLdcInsn(StringUtils.getFileName(className!!))\n        // 将当前方法名添加到操作栈，进行输出\n        mv.visitLdcInsn(name)\n        mv.visitMethodInsn(Opcodes.INVOKESTATIC, \"android/util/Log\", \"d\", \"(Ljava/lang/String;Ljava/lang/String;)I\", false)\n    }\n}\n```\n\n首先调用 `visitLdcInsn` 将类名和方法名推入到操作数栈，然后通过 `visitMethodInsn` 方法调用 `Log.d(String tag, String msg)` 进行日志的输出。\n\n##### 示例\n\n原始的代码片段：\n\n```kotlin\npublic class MainActivity extends AppCompatActivity {\n    public void testLog() {\n        Log.d(\"TAG\",\"dsw\");\n        Log.i(\"TAG\",\"dsw\");\n        Log.v(\"TAG\",\"dsw\");\n        Log.w(\"TAG\",\"dsw\");\n        Log.e(\"TAG\",\"dsw\");\n    }\n}\n```\n\n查看处理后的 `.class` 文件中的代码片段：\n\n```kotlin\npublic class MainActivity extends AppCompatActivity {\n    public String testString(String var1, String var2) {\n        Log.d(\"MainActivity\", \"testString\");\n        int var3 = 5 / 0;\n        return \"HelloWorld\";\n    }\n}\n```\n\n可以看到已经在方法刚进入时插入了日志输出。\n\n#### 4. 添加 try...catch 异常捕捉\n\n##### 目标\n\n给项目中所有方法添加 `try...catch` 块，并调用 `Exception.printStackTrace` 输出日志。\n\n##### 思路\n\n将所有的方法添加 `try...catch` 块，意味着方法的整个实现是在 `try` 块中，然后我们插入 `catch` 块的实现即可。在 `MethodVisitor` 类中提供了 `visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type)` 用于生成一个 `try...catch` 块，其中 `start` 表示其实位置，`end` 表示结束位置，`handler` 表示 `Exception` 开始的位置，`tpye` 表示异常参数的类型。所以我们在方法访问开始前，先调用 `visitLabel` 定义起始位置，然后在方法结束前调用 `visitLable` 定义结束位置。有个细节需要注意，`catch` 块中需要根据方法的返回值类型添加异常时的返回值。\n\n##### 关键代码实现\n\n```kotlin\nclass TryCatchInterceptor(methodVisitor: MethodVisitor, access: Int, name: String?, var descriptor: String?) :\n        AdviceAdapter(PluginConstant.ASM_VERSION, methodVisitor, access, name, descriptor) {\n    private val labelStart = Label()\n    private val labelEnd = Label()\n    private val labelTarget = Label()\n    override fun onMethodEnter() {\n        // 定义开始位置\n        mv.visitLabel(labelStart)\n        // 开始 try...catch 块\n        mv.visitTryCatchBlock(labelStart, labelEnd, labelTarget, \"java/lang/Exception\")\n    }\n\n    override fun visitMaxs(maxStack: Int, maxLocals: Int) {\n        // 定义正常代码结束的位置\n        mv.visitLabel(labelEnd)\n        // 定义 catch 块开始的位置\n        mv.visitLabel(labelTarget)\n        val local1 = newLocal(Type.getType(\"Ljava/lang/Exception\"))\n        mv.visitVarInsn(Opcodes.ASTORE, local1)\n        mv.visitVarInsn(Opcodes.ALOAD, local1)\n        // 输出 ex.printStackTrace\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, \"java/lang/Exception\", \"printStackTrace\", \"()V\", false)\n        //判断方法的返回类型\n        mv.visitInsn(getReturnCode(descriptor = descriptor))\n        super.visitMaxs(maxStack, maxLocals)\n    }\n\n    /**\n     * 获取对应的返回值\n     */\n    private fun getReturnCode(descriptor: String?): Int {\n        return when (descriptor!!.subSequence(descriptor.indexOf(\")\") + 1, descriptor.length)) {\n            \"V\" -\u003e Opcodes.RETURN\n            \"I\", \"Z\", \"B\", \"C\", \"S\" -\u003e {\n                mv.visitInsn(Opcodes.ICONST_0)\n                Opcodes.IRETURN\n            }\n            \"D\" -\u003e {\n                mv.visitInsn(Opcodes.DCONST_0)\n                Opcodes.DRETURN\n            }\n            \"J\" -\u003e {\n                mv.visitInsn(Opcodes.LCONST_0)\n                Opcodes.LRETURN\n            }\n            \"F\" -\u003e {\n                mv.visitInsn(Opcodes.FCONST_0)\n                Opcodes.FRETURN\n            }\n            else -\u003e {\n                mv.visitInsn(Opcodes.ACONST_NULL)\n                Opcodes.ARETURN\n            }\n        }\n    }\n}\n```\n\n##### 案例\n\n原始代码片段：\n\n```kotlin\npublic class MainActivity extends AppCompatActivity {\n    public void testLog() {\n        Log.d(\"TAG\",\"dsw\");\n        Log.i(\"TAG\",\"dsw\");\n        Log.v(\"TAG\",\"dsw\");\n        Log.w(\"TAG\",\"dsw\");\n        Log.e(\"TAG\",\"dsw\");\n    }\n}\n```\n\n查看处理后的 `.class` 文件中的代码片段：\n\n```kotlin\npublic class MainActivity extends AppCompatActivity {\n    public String testString(String var1, String var2) {\n        try {\n            int var3 = 5 / 0;\n            return \"HelloWorld\";\n        } catch (Exception var5) {\n            var5.printStackTrace();\n            return null;\n        }\n    }\n}\n```\n\n添加 `try...catch` 块复杂一些，那有的同学该疑惑了？字节码我不了解该怎么写呢？这里给大家安利一个工具 [ASM Bytecode Outline](https://plugins.jetbrains.com/plugin/5918-asm-bytecode-outline)，可以直接安装在 `IDE` 中，然后右键 **Show Bytecode outline** 在对应的文件上，弹出的面板展示三项内容：\n\n- `Bytecode` ：表示对应的 `.class` 字节码文件\n- `ASMified` ：表示使用 `ASM` 框架生成字节码时对应的代码\n- `Groovified` ：对应的是 `.class` 字节码指令\n\n![asm_outline](./doc/asm_outline.png)\n\n#### 5. 总结\n\n字节码增强技术可以在编译时动态修改字节码，典型的场景比如埋点插桩，同时也可用于一些线上问题定位于修复以及在开发中减少冗余代码，大大提高开发效率。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeansoft%2Fasm-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeansoft%2Fasm-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeansoft%2Fasm-example/lists"}