{"id":15288358,"url":"https://github.com/sy007/debounceplugin","last_synced_at":"2025-04-06T00:10:27.518Z","repository":{"id":52078815,"uuid":"317844079","full_name":"sy007/DebouncePlugin","owner":"sy007","description":"Android点击事件防抖动插件","archived":false,"fork":false,"pushed_at":"2024-11-02T15:52:22.000Z","size":482,"stargazers_count":117,"open_issues_count":4,"forks_count":16,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T23:11:11.062Z","etag":null,"topics":["android","gradle","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":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sy007.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-02T11:43:11.000Z","updated_at":"2025-02-11T08:27:49.000Z","dependencies_parsed_at":"2025-01-28T03:11:15.253Z","dependency_job_id":"2629595c-f6e5-4239-8040-00628b792254","html_url":"https://github.com/sy007/DebouncePlugin","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sy007%2FDebouncePlugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sy007%2FDebouncePlugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sy007%2FDebouncePlugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sy007%2FDebouncePlugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sy007","download_url":"https://codeload.github.com/sy007/DebouncePlugin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247415973,"owners_count":20935387,"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","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":["android","gradle","gradle-plugin"],"created_at":"2024-09-30T15:47:54.563Z","updated_at":"2025-04-06T00:10:27.495Z","avatar_url":"https://github.com/sy007.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DebouncePlugin\n\nAndroid点击事件防抖动插件，主要为了解决项目以及第三方库中快速点击问题。\n\n## 1.支持以下功能：\n\n1. 支持AGP8.0\n2. 支持application ,library 中使用\n3. 支持Java,Kotlin点击事件防抖\n4. 支持Java,Kotlin Lambda点击事件防抖\n5. 支持排除或处理指定路径下的代码防抖处理**(文件级黑白名单)**，就跟写gitignore一样简单\n6. 支持排除或处理指定方法防抖处理**(方法级黑白名单)**，两个注解解决你的问题\n7. 支持多种类型的点击事件处理。例如：\n   - ListView#onItemClick\n   - ListView#onItemSelected\n   - ExpandableListView#onGroupClick\n   - ExpandableListView#onChildClick\n   - ...只要你想处理，都支持。\n8. 支持xml设置的点击事件防抖\n9. 支持ButterKnife,XUtils等三方APT设置的点击事件防抖\n10. 支持自定义防抖处理\n\n   1. 一段时间内，只允许触发一次点击事件，时间多久你说了算\n\n   2. 可以每个点击事件防抖状态唯一，也可以全局共享一个防抖状态\n   3. 支持运行时二次拦截处理\n   4. 甚至可以做到全局点击事件埋点\n11. 代码修改透明(插件对代码的修改会生成一个html报告)\n\n## 2.如何使用\n\n1. 在你项目的build.gradle依赖如下：\n\n```groovy\nbuildscript {\n    ...\n    dependencies {\n        //依赖插件所需的环境\n        classpath 'io.github.sy007:debounce-plugin:2.1.5'\n    }\n}\n\nallprojects {\n    repositories {\n        //添加mavenCentral仓库\n        mavenCentral()\n    }\n}\n```\n\n2. 在app module中依赖如下：\n\n```groovy\napply plugin: 'com.android.application'\n//应用插件\napply plugin: 'debounce-plugin'\nandroid{\n   ...\n}\n//插件配置\ndebounce {\n  proxyClassName = \"$配置自定义代理类\"\n}\ndependencies {\n    //插件所需的依赖库\n    implementation 'io.github.sy007:debounce-lib:2.1.5'\n}\n```\n\n\n\n## 3.自定义配置\n\n插件支持自定义配置，在你的app#build.gradle配置如下：\n\n```groovy\ndebounce {\n    proxyClassName = \"$配置自定义代理类\"\n    generateReport = true\n    includes = [\"$填写需要事件防抖的目录或文件\"]\n    excludes = [\"$填写不需要事件防抖的目录或文件\"]\n    excludeForMethodAnnotation = [\"$填写不需要事件防抖的方法上注解信息\"]\n}\n```\n\n| 参数                       | 是否必须 |\n| -------------------------- | -------- |\n| proxyClassName             | 是       |\n| generateReport             | 否       |\n| includes                   | 否       |\n| excludes                   | 否       |\n| excludeForMethodAnnotation | 否       |\n\n1. proxyClassName: 自定义代理类，其目的是为了配置hook信息。比如在你工程下创建`ClickMethodProxy.java`类。\n\n   ```java\n   package com.example.gradleplugin;\n   import android.view.View;\n   import android.widget.AdapterView;\n   import com.sunyuan.debounce.lib.BounceChecker;\n   import com.sunyuan.debounce.lib.ClickDeBounce;\n   import com.sunyuan.debounce.lib.AnnotationMethodProxy;\n   import com.sunyuan.debounce.lib.InterfaceMethodProxy;\n   import com.sunyuan.debounce.lib.MethodHookParam;\n   import butterknife.OnClick;\n   import butterknife.OnItemClick;\n   /**\n    * @author sy007\n    * @date 2023/01/17\n    * @description\n    */\n   public class ClickMethodProxy {\n   \n       /**\n        * 多长事件内只触发一次点击事件\n        */\n       private static final long CHECK_TIME = 1000;\n   \n       /**\n        * 防抖判断工具类\n        */\n       private final BounceChecker checker = new BounceChecker();\n   \n       /**\n        * 处理{@link View.OnClickListener#onClick(View)}点击事件防抖\n        * \u003cp\u003e\n        * 根据{@link InterfaceMethodProxy}注解上的配置，插件扫描到{@link View.OnClickListener#onClick(View)}时\n        * 会调用该方法，你可以从{@link MethodHookParam}中取出点击事件所属的类和方法名以及参数来做防抖判断\n        *\n        * @param param 事件方法描述\n        * @return 返回true表示拦截，false则不拦截\n        */\n       @InterfaceMethodProxy(\n               ownerType = View.OnClickListener.class,\n               methodName = \"onClick\",\n               parameterTypes = {View.class},\n               returnType = void.class)\n       public boolean onClickProxy(MethodHookParam param) {\n           /**\n            * {@link View.OnClickListener#onClick(View)}只有一个参数View，所以直接取\n            */\n           if (param.args[0] instanceof CheckBox) {\n               return false;\n           }\n           View view = (View) param.args[0];\n           boolean isBounce = checker.checkView(param.owner, param.methodName, view, CHECK_TIME);\n           LogUtil.d(\"onClickProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n           return isBounce;\n       }\n     \n     \n    \t /**\n        * 处理xml中设置的点击事件防抖\n        * \u003cp\u003e\n        * 根据{@link AnnotationMethodProxy}注解上的配置，插件扫描到声明{@link ClickDeBounce}注解的方法时\n        * 会调用该方法,你可以从{@link MethodHookParam}中取出点击事件所属的类和方法名以及参数来做防抖判断\n        * \u003cp\u003e\n        * 注意:{@link ClickDeBounce}注解必须声明在有切仅有一个View参数的方法上，这个注解是为了解决xml中设置的点击事件防抖\n        *\n        * @param param 事件方法描述\n        * @return 返回true表示拦截，false则不拦截\n        */\n       @AnnotationMethodProxy(type = ClickDeBounce.class)\n       public boolean onClickDeBounceAnnotationProxy(MethodHookParam param) {\n           /**\n            * {@link ClickDeBounce}声明在有切仅有一个View参数的方法上，所以直接取\n            */\n           View view = (View) param.args[0];\n           boolean isBounce = checker.checkView(param.owner, param.methodName, view, CHECK_TIME);\n           LogUtil.d(\"onClickDeBounceAnnotationProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n           return isBounce;\n       }\n   }\n   \n   ```\n\n   那么`proxyClassName`填写就是`com.example.gradleplugin.ClickMethodProxy`,插件会根据`ClickMethodProxy`中的注解配置来决定hook哪些点击事件并调用这些方法。\n\n   根据`ClickMethodProxy`代码中定义，插件提供两个注解:\n\n   1. InterfaceMethodProxy: 配置需要hook的事件信息\n\n   ```java\n   @Target(ElementType.METHOD)\n   @Retention(RetentionPolicy.RUNTIME)\n   public @interface InterfaceMethodProxy {\n       //事件所属的接口类型\n       Class\u003c?\u003e ownerType();\n   \n       //事件所属的方法名\n       String methodName();\n   \n       //事件所属的方法参数列表类型\n       Class\u003c?\u003e[] parameterTypes();\n   \n       //事件方法的返回类型\n       Class\u003c?\u003e returnType();\n   }\n   ```\n\n   2. AnnotationMethodProxy: 配置需要hook的方法(**方法上声明AnnotationMethodProxy配置的注解，都会被hook**)\n\n   ```java\n   @Target(ElementType.METHOD)\n   @Retention(RetentionPolicy.RUNTIME)\n   public @interface AnnotationMethodProxy {\n       //注解类型\n       Class\u003c? extends Annotation\u003e type();\n   }\n   ```\n\n   **注意:自定义`ClickMethodProxy`中被InterfaceMethodProxy和AnnotationMethodProxy修饰的方法必须满足下面三个条件:**\n\n   - **非静态**\n   - **方法参数必须是MethodHookParam且只有一个参数**\n   - **返回值必须是boolean**\n\n2. generateReport：是否生成方法修改报告，即插件修改的方法会生成一份html报告，报告路径:app/build/reports/debounce-plugin/${buildType}/modified-method-list.html\n\n3. includes： 处理指定路径下的代码事件防抖(文件级白名单),类似于.gitignore 编写规则\n\n4. excludes：排除指定路径下的代码事件防抖(文件级黑名单),类似于.gitignore 编写规则\n\n5. excludeForMethodAnnotation：方法级别黑名单,方法上声明了这些注解，那么该方法不会插入防抖代码。插件内部默认添加了` IgnoreClickDeBounce`注解，即声明在方法上的`IgnoreClickDeBounce`注解，都不会插入防抖代码。**注意:这里配置的是注解的字节码**\n\n   ```kotlin\n   //插件内部自动添加了IgnoreClickDeBounce注解\n   excludeForMethodAnnotation.add(\"Lcom/sunyuan/debounce/lib/IgnoreClickDeBounce;\")\n   ```\n\n## 4.运行说明\n\n集成完毕后需要同步下，插件会输出如下日志：\n\n  ```json\n------------------debounce plugin config info--------------------\n{\n    \"generateReport\": true,\n    \"proxyClassName\": \"com.example.gradleplugin.ClickMethodProxy\",\n    \"includes\": [\n        \n    ],\n    \"excludes\": [\n        \"com/example/gradleplugin/excludes/*\",\n        \"androidx/**/*\",\n        \"android/**/*\",\n        \"com/google/android/**/*\",\n        \"butterknife/internal/DebouncingOnClickListener.class\",\n        \"**/*_ViewBinding*.class\"\n    ],\n    \"excludeForMethodAnnotation\": [\n        \"Lcom/sunyuan/debounce/lib/IgnoreClickDeBounce;\"\n    ]\n}\n-----------------------------------------------------------------\n  ```\n\n运行apk或执行  `./gradlew clean`  `./gradlew  assembleDebug` \n\n在执行过程中控制台会输出自定义代理类解析后的hook信息\n\n```json\n------------------proxy class config info--------------------\n{\n    \"owner\": \"com/example/gradleplugin/ClickMethodProxy\",\n    \"annotationIndex\": {\n        \"Lcom/sunyuan/debounce/lib/ClickDeBounce;\": {\n            \"methodDesc\": \"(Lcom/sunyuan/debounce/lib/MethodHookParam;)Z\",\n            \"methodName\": \"onClickDeBounceAnnotationProxy\"\n        },\n        \"Lbutterknife/OnItemClick;\": {\n            \"methodDesc\": \"(Lcom/sunyuan/debounce/lib/MethodHookParam;)Z\",\n            \"methodName\": \"onItemClickWithButterKnifeProxy\"\n        },\n        \"Lbutterknife/OnClick;\": {\n            \"methodDesc\": \"(Lcom/sunyuan/debounce/lib/MethodHookParam;)Z\",\n            \"methodName\": \"onClickWithButterKnifeProxy\"\n        }\n    },\n    \"methodIndex\": [\n        {\n            \"samMethodEntity\": {\n                \"owner\": \"android/view/View$OnClickListener\",\n                \"methodDesc\": \"(Landroid/view/View;)V\",\n                \"methodName\": \"onClick\"\n            },\n            \"proxyMethodEntity\": {\n                \"methodDesc\": \"(Lcom/sunyuan/debounce/lib/MethodHookParam;)Z\",\n                \"methodName\": \"onClickProxy\"\n            }\n        },\n        {\n            \"samMethodEntity\": {\n                \"owner\": \"android/widget/AdapterView$OnItemClickListener\",\n                \"methodDesc\": \"(Landroid/widget/AdapterView;Landroid/view/View;IJ)V\",\n                \"methodName\": \"onItemClick\"\n            },\n            \"proxyMethodEntity\": {\n                \"methodDesc\": \"(Lcom/sunyuan/debounce/lib/MethodHookParam;)Z\",\n                \"methodName\": \"onItemClickProxy\"\n            }\n        }\n    ]\n}\n```\n\n插件执行完毕后控制台输出插件执行耗时以及修改报告地址:\n\n```java\n\u003e Task :app:transformClassesWithDebounceTransformForDebug\n--------------------------------------------------------\nDebounceTransform cost 2918ms\n--------------------------------------------------------\n--------------------------------------------------------\ndebounce-transform-report:xxx/app/build/reports/debounce-plugin/debug/modified-method-list.html\n--------------------------------------------------------\n\n```\n\n打开报告,报告中详细列出了插件修改的方法:\n\n![](http://m.qpic.cn/psc?/V51CSwpO1slVFI402aSY2YlJCy2S2DcR/bqQfVz5yrrGYSXMvKr.cqU69DlyLgGD4TtKgaP3Y5.H70w.bIE5CkzyPFY7A83Pch6RuSrN92z5lAX5Q17UScXQ38vcNaDSeVV6EaBNjf3g!/b\u0026bo=lgoGB5YKBgcDByI!\u0026rf=viewer_4)\n\nDemo运行起来后，点击页面上的按钮如图所示:\n\n![image](http://m.qpic.cn/psc?/V11vVsP84HfNn2/bqQfVz5yrrGYSXMvKr.cqYpqJxqZga9c8eRhMoRWXwHxrrSsyw*fZlgaKBa76ZLChc7DBNiVUQG1NL3wYexkfna5GwRPuhxhkk*cEm4Ena4!/b\u0026bo=6AooBugKKAYDByI!\u0026rf=viewer_4)\n\n## 5.FAQ\n\n### 5.1 插件提供了include, exclude和excludeForMethodAnnotation，他们在事件防抖中起到什么作用，以及他们之间的优先级是怎样的？\n\n#### 5.1.1 背景\n\n插件提供`include`，`exclude`，和`excludeForMethodAnnotation` 主要解决事件防抖个性化的场景，不是每个应用都需要处理全局事件防抖。\n\n于是有了`include`和`exclude`用于处理或排除文件级别的事件防抖。那还有一种场景是某个方法不需要防抖，于是插件提供了`excludeForMethodAnnotation`排除方法级别的防抖。\n\n#### 5.1.2 优先级\n\n`exclude`优先级高于`include`\n\n`include`优先级高于`excludeForMethodAnnotation`\n\n分为两个步骤:\n\n1. 插件执行时会遍历所有class文件，根据`exclude`的配置排除某些class文件处理，剩余的class文件再根据`include`配置判断是否需要处理\n2. 第一步结束后会得到需要处理的class文件，然后遍历每一个class的method列表，通过`excludeForMethodAnnotation`的配置排除某个方法\n\n### 5.2 为什么修改了debounce配置没有生效?\n\n修改debounce任何配置都需要Build-\u003eclean Project，然后在运行项目，否则新修改的配置不会生效\n\n### 5.3 为什么生成的报告不全？\n\n这是因为插件支持增量更新，哪个文件改动了，只会处理该文件的事件防抖，所以输出的报告只有该文件中的防抖(修改)的方法信息，如果要产生全量的方法防抖(修改)报告，需要Build-\u003eclean Project，然后运行项目。\n\n### 5.4 如何查看插件修改后的代码?\n\n查看路径：app\\build\\intermediates\\transforms\\DebounceTransform\\xxx\n\n### 5.5 如何关闭插件功能？\n\n在工程的`gradle.properties`中配置\n\n```properties\n#关闭防抖动插件\ndebounceEnable=false\n```\n\n同步gradle,日志输出:\n\n```\ndebounce-plugin is off.\n```\n\n说明插件功能已关闭\n\n### 5.6 如何对ButterKnife等三方APT设置的点击事件防抖处理？\n\nAPT会生成模版类，在模版类中依然使用的是原生的点击事件。既然是原生的点击事件，那插件根据自定义`ClickMethodProxy.java`中配置信息就能处理，为什么还要说明下**如何对ButterKnife等三方APT设置的点击事件防抖处理呢？**\n\n假设项目中使用了`ButterKnife`,只想对主工程下的代码事件防抖，其他模块下的不处理。按照插件配置规则需要配置`include[$主工程代码路径]`。插件根据include配置的路径筛选出处理的class时\n\n由于`ButterKinfe`通过APT设置的`onClick`使用`DebouncingOnClickListener`包装了一层。如下所示:\n\n```java\n  @UiThread\n  public MainActivity_ViewBinding(final MainActivity target, View source) {\n    view.setOnClickListener(new DebouncingOnClickListener() {\n      @Override\n      public void doClick(View p0) {\n        target.onClick();\n      }\n    });\n  }\n```\n\n```java\npackage butterknife.internal;\n\npublic abstract class DebouncingOnClickListener implements View.OnClickListener {\n  private static final Runnable ENABLE_AGAIN = () -\u003e enabled = true;\n  private static final Handler MAIN = new Handler(Looper.getMainLooper());\n  static boolean enabled = true;\n  @Override public final void onClick(View v) {\n    if (enabled) {\n      enabled = false;\n      MAIN.post(ENABLE_AGAIN);\n      doClick(v);\n    }\n  }\n  public abstract void doClick(View v);\n}\n```\n\n根据`include[$主工程代码路径]`配置，所以`DebouncingOnClickListener#onClick`不会插入防抖代码，即ButterKnife设置的点击事件不会有防抖功能\n\n需要配置以下策略,以正确处理ButterKnife设置的事件防抖\n\n```groovy\ndebounce {\n    proxyClassName = \"com.example.gradleplugin.ClickMethodProxy\"\n    includes = [$主工程代码路径]\n    /**\n     * 排除ButterKnife生成的模版类\n     * ButterKnife事件防抖由自定义`ClickMethodProxy.java`中的配置保证\n     */\n    excludes = [\"**/*_ViewBinding*.class\"]\n}\n```\n\n```java\npackage com.example.gradleplugin;\nimport android.view.View;\nimport com.sunyuan.debounce.lib.BounceChecker;\nimport com.sunyuan.debounce.lib.AnnotationMethodProxy;\nimport com.sunyuan.debounce.lib.MethodHookParam;\nimport butterknife.OnClick;\nimport butterknife.OnItemClick;\n/**\n * @author sy007\n * @date 2023/01/17\n * @description\n */\npublic class ClickMethodProxy {\n    /**\n     * 多长事件内只触发一次点击事件\n     */\n    private static final long CHECK_TIME = 1000;\n\n    /**\n     * 防抖判断工具类\n     */\n    private final BounceChecker checker = new BounceChecker();\n\n    /**\n     * 处理ButterKnife OnItemClick点击事件防抖\n     * \u003cp\u003e\n     * 根据{@link AnnotationMethodProxy}注解上的配置，插件扫描到声明{@link OnItemClick}注解的方法时\n     * 会调用该方法,你可以从{@link MethodHookParam}中取出点击事件所属的类和方法名以及参数来做防抖判断\n     * \u003cp\u003e\n     * 注意:{@link OnItemClick}属于ButterKnife中的注解，在使用时方法参数可以写，也可以不写，甚至不写全都行\n     * 所以这里生成点击事件唯一标识只能拼接方法所属的类+方法名+方法参数{@link MethodHookParam#generateUniqueId()}\n     *\n     * @param param 事件方法描述\n     * @return 返回true表示拦截，false则不拦截\n     */\n    @AnnotationMethodProxy(type = OnItemClick.class)\n    public boolean onItemClickWithButterKnifeProxy(MethodHookParam param) {\n        boolean isBounce = checker.checkAny(param.generateUniqueId(), CHECK_TIME);\n        LogUtil.d(\"onItemClickWithButterKnifeProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n        return isBounce;\n    }\n\n    /**\n     * 处理ButterKnife OnClick点击事件防抖\n     * \u003cp\u003e\n     * 根据{@link AnnotationMethodProxy}注解上的配置，插件扫描到声明{@link OnClick}注解的方法时\n     * 会调用该方法,你可以从{@link MethodHookParam}中取出点击事件所属的类和方法名以及参数来做防抖判断\n     * \u003cp\u003e\n     * 注意:{@link OnClick}属于ButterKnife中的注解，在使用时方法参数可以写，也可以不写\n     * \u003cp\u003e\n     * 所以这里生成点击事件唯一标识的逻辑是事件方法的参数判断:\n     * \u003cp\u003e\n     * 有一个参数这个参数就是{@link View}调用{@link BounceChecker#checkView(String, String, View, long)}就可以了，\n     * 没有参数调用{@link BounceChecker#checkAny(String, long)}\n     *\n     * @param param 事件方法描述\n     * @return 返回true表示拦截，false则不拦截\n     */\n    @AnnotationMethodProxy(type = OnClick.class)\n    public boolean onClickWithButterKnifeProxy(MethodHookParam param) {\n        boolean isBounce;\n        if (param.args.length != 0) {\n            isBounce = checker.checkView(param.owner, param.methodName, (View) param.args[0], CHECK_TIME);\n        } else {\n            isBounce = checker.checkAny(param.generateUniqueId(), CHECK_TIME);\n        }\n        LogUtil.d(\"onClickWithButterKnifeProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n        return isBounce;\n    }\n}\n\n\n```\n\n\n\n### 5.7 如何对xml中设置的点击事件防抖处理？\n\n#### 5.7.1 原理\n\n在View源码中会解析xml属性，如设置了onClick属性，会创建一个`DeclaredOnClickListener`设置给当前View,在收到点击时间时，反射调用xml中onClick属性指定的方法。**由于View是android.jar包下的类，只参与编译，所以无法利用插桩对android.jar下的类插入自己的代码**。\n\n僵硬，难道走不通了吗？其实不然，我们现在的Activity都是继承AppCompatActivity,在AppCompatActivity对LayoutInflater设置自定义解析，如果xml中设置了onClick，则会创建一个`DeclaredOnClickListener`设置给当前View,在收到点击时间时，反射调用xml中onClick指定的方法。看起来好像跟android.jar包下View的处理一样，但是**AppCompatActivity是androidx.appcompat:appcompat:x.y.z 包下的，这样我们就可以愉快的插入自己的代码了。**\n\n#### 5.7.2 处理\n\n5.6.1中描述了xml中设置的点击事件防抖处理原理，一般情况下无需配置，插件已帮你处理了。但是如果事件防抖处理有严格的规则。如果只想处理主工程下的事件的防抖，那么这种情况下就需要特殊配置了,和ButterKnife处理类似。\n\n```java\n/**\n * xml中设置的点击事件\n */\n@ClickDeBounce\npublic void reflectOnClick(View view) {\n    LogUtil.d(\"xml设置onClick事件\");\n}\n```\n\n```groovy\n//插件配置\ndebounce {\n    proxyClassName = \"com.example.gradleplugin.ClickMethodProxy\"\n    includes = [$主工程代码路径]\n}\n```\n\n```java\npackage com.example.gradleplugin;\nimport android.view.View;\nimport com.sunyuan.debounce.lib.BounceChecker;\nimport com.sunyuan.debounce.lib.AnnotationMethodProxy;\nimport com.sunyuan.debounce.lib.ClickDeBounce;\nimport com.sunyuan.debounce.lib.MethodHookParam;\n/**\n * @author sy007\n * @date 2023/01/17\n * @description\n */\npublic class ClickMethodProxy {\n\n    /**\n     * 多长事件内只触发一次点击事件\n     */\n    private static final long CHECK_TIME = 1000;\n\n    /**\n     * 防抖判断工具类\n     */\n    private final BounceChecker checker = new BounceChecker();\n\n    /**\n     * 处理xml中设置的点击事件防抖\n     * \u003cp\u003e\n     * 根据{@link AnnotationMethodProxy}注解上的配置，插件扫描到声明{@link ClickDeBounce}注解的方法时\n     * 会调用该方法,你可以从{@link MethodHookParam}中取出点击事件所属的类和方法名以及参数来做防抖判断\n     * \u003cp\u003e\n     * 注意:{@link ClickDeBounce}注解必须声明在有切仅有一个View参数的方法上，这个注解是为了解决xml中设置的点击事件防抖\n     *\n     * @param param 事件方法描述\n     * @return 返回true表示拦截，false则不拦截\n     */\n    @AnnotationMethodProxy(type = ClickDeBounce.class)\n    public boolean onClickDeBounceAnnotationProxy(MethodHookParam param) {\n        /**\n         * {@link ClickDeBounce}声明在有且仅有一个View参数的方法上，所以直接取\n         */\n        View view = (View) param.args[0];\n        boolean isBounce = checker.checkView(param.owner, param.methodName, view, CHECK_TIME);\n        LogUtil.d(\"onClickDeBounceAnnotationProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n        return isBounce;\n    }\n}\n\n```\n\n### 5.8 如何排除某个事件方法防抖处理？\n\n在不需要防抖的事件方法上声明`@IgnoreClickDeBounce`注解\n\n```java\nfindViewById(R.id.btn_ignore_click_debounce).setOnClickListener(new View.OnClickListener() {\n    @IgnoreClickDeBounce\n    @Override\n    public void onClick(View v) {\n        LogUtil.d(\"忽略点击防抖\");\n    }\n});\n```\n\n### 5.9 运行时二次拦截呢？\n\n这个功能源自于一位老哥的反馈\n\n![](http://photogz.photo.store.qq.com/psc?/V11vVsP84HfNn2/bqQfVz5yrrGYSXMvKr.cqQbTJqTscVPZ7nnrNG8dvTUxjuqR0GHINQfY**t3p13gOqQkpdOLyzsJcimqj2.J9C5xoB*9jRrgYdW.9xfMlek!/b\u0026bo=ZgiAAmYIgAIDByI!\u0026rf=viewer_4)\n\nCheckBox显示状态和点击事件处理时获取的状态不一致。用户快速点击两次，页面上CheckBox从未选中状态-\u003e选中状态-\u003e未选中状态。而点击事件只执行了一次。此时点击事件中只执行了选中状态的事件。\n\n这种情况如何处理呢？\n\n很简单，只需在自定义代理类中代理onClick的方法中过滤掉CheckBox的防抖处理即可\n\n```java\n@InterfaceMethodProxy(\n  ownerType = View.OnClickListener.class,\n  methodName = \"onClick\",\n  parameterTypes = {View.class},\n  returnType = void.class)\npublic boolean onClickProxy(MethodHookParam param) {\n  /**\n  * {@link View.OnClickListener#onClick(View)}只有一个参数View，所以直接取\n  */\n  if (param.args[0] instanceof CheckBox) {\n    //解决给CheckBox设置点击事件时页面显示状态和事件处理状态不一致问题，这里对CheckBox就不防抖处理了。\n    return false;\n  }\n  View view = (View) param.args[0];\n  boolean isBounce = checker.checkView(param.owner, param.methodName, view, CHECK_TIME);\n  LogUtil.d(\"onClickProxy=\u003e\" + \"[isBounce:\" + isBounce + \",checkTime:\" + CHECK_TIME + \"]\");\n  return isBounce;\n}\n```\n\n## 6.更新日志\n\n### 2.1.5\n\n1. 修复Gradle版本判断错误问题[#25](https://github.com/sy007/DebouncePlugin/issues/25)\n\n### 2.1.3\n\n1. 修复Unsupported api xxx问题[#20](https://github.com/sy007/DebouncePlugin/issues/20)\n\n### 2.1.2\n\n1. 支持APG8.0 [#19](https://github.com/sy007/DebouncePlugin/issues/19)\n\n### 2.0.4\n\n1. 修复kotlin写法的代理类插桩无效问题 [#16](https://github.com/sy007/DebouncePlugin/issues/16)\n\n### 2.0.3\n\n1. 修复Java8 default method 插桩异常问题 [#13](https://github.com/sy007/DebouncePlugin/issues/13)\n\n### 2.0.1\n\n1. 修复编译期Invalid stack map table问题 [#10](https://github.com/sy007/DebouncePlugin/issues/10#issuecomment-1430955462)\n\n### 2.0.0\n\n1. 插件底层逻辑重构\n\n2. 支持自定义防抖处理\n\n3. 移除插件配置:\n\n   1. isDebug \n\n   2. checkTime\n\n   3. includeForMethodAnnotation \n\n   4. methodEntities \n\n      **以上被移除的配置，所表示的功能由自定义代理类代替**\n\n### 1.2.0\n\n1. 支持Library中使用debounce-plugin \n\n### 1.1.3\n\n1. 适配JDK9,11,17 inDy指令解析 [#6](https://github.com/sy007/DebouncePlugin/issues/6)\n\n### 1.1.2\n\n1. 修复checkTime配置无效问题 [#6](https://github.com/sy007/DebouncePlugin/issues/7#issuecomment-1296265701)\n\n### 1.1.1\n\n1. 修复Lambda表达式字节码解析异常 [#6](https://github.com/sy007/DebouncePlugin/issues/6)\n2. 修复kotlin方法引用异常 [#7](https://github.com/sy007/DebouncePlugin/issues/7)\n\n### 1.1.0\n\n1. 解决D8 warning:Expected stack map table for method with non-linear control flow\n2. transform性能提升50%（感谢booster）\n\n### 1.0.2\n\n1. 完善lambda表达式插桩处理\n2. 解决kotlin plugin 1.7.10版本，lambda实例方法引用插桩失败问题\n\n### 1.0.1\n\n修复ClassReader.superName为空问题\n\n### 1.0.0\n\n1. 新增ClickDeBounce和IgnoreClickDeBounce注解,用于声明方法是否需要插桩\n2. 将debounceCheckTime命名修改为checkTime\n3. 新增includeForMethodAnnotation和excludeForMethodAnnotation配置, 用于控制方法是否插桩\n4. 新增方法修改报告\n5. 多线程优化插桩速度\n\n### 0.4.1\n\n新增`gradle.properties`配置属性`debounceEnable`是否关闭插件功能\n\n### 0.3.0\n\n升级`debounce-plugin`依赖的ASM版本到7.3.1版本\n\n### 0.2.0\n\n1. 修复自定义View中运行时点击按钮出现闪退问题\n\n- 针对子类重写父类的方法时,子类和父类中都插入判断代码\n\n - 构造函数中创建检测工具类延迟到点击方法被调用时\n\n2. 调整日志打印\n\n### 0.1.0\n\n项目初始化","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsy007%2Fdebounceplugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsy007%2Fdebounceplugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsy007%2Fdebounceplugin/lists"}