news 2026/4/16 21:10:28

Android 全局防抖/防重复点击

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 全局防抖/防重复点击

1.下载资源,导入到项目。
2.在你项目中创建一个FastClickHelper,比如

/*** * 插桩。只插在本项目中。其他第三方的不管 */ public class FastClickHelper { private static final long DEFAULT_INTERVAL_MS = 500L; // 用于存储自定义间隔的 Tag Key private static final int TAG_LAST_TIME = com.csii.baseutil.R.id.fastclick_last_time; private static final int TAG_CUSTOM_INTERVAL = com.csii.baseutil.R.id.fastclick_custom_interval; private static final int TAG_DISABLE = com.csii.baseutil.R.id.fastclick_disabled; public static boolean isFastClick(View view) { if (view == null) return true; // 添加详细日志 // String viewInfo = "View=" + view.getClass().getSimpleName() + // ", ID=" + view.getId() + // ", Tag=" + view.getTag(); // Log.i("FastClick", "isFastClick called: " + viewInfo); // 检查是否对该 View 禁用了防抖 Boolean disabled = (Boolean) view.getTag(TAG_DISABLE); if (disabled != null && disabled) { return false; // 不禁用,直接放行 } // 获取为该 View 单独设置的间隔,如果没有则使用默认值 Long customInterval = (Long) view.getTag(TAG_CUSTOM_INTERVAL); long interval = (customInterval != null) ? customInterval : DEFAULT_INTERVAL_MS; Long lastTime = (Long) view.getTag(TAG_LAST_TIME); long currentTime = System.currentTimeMillis(); if (lastTime == null || currentTime - lastTime >= interval) { view.setTag(TAG_LAST_TIME, currentTime); Log.i("FastClick","非快速点击,放行"); return false; } Log.i("FastClick","快速点击,拦截"); return true; } public static void setCustomInterval(View view, long intervalMs) { view.setTag(TAG_CUSTOM_INTERVAL, intervalMs); } /*** * 禁止某个view 插桩 * @param view * @param disabled */ public static void setDisabled(View view, boolean disabled) { view.setTag(TAG_DISABLE, disabled); } }

3.修改doubleClick lib包下的FastClickMethodVisitor文件中 修改为自己的包名,就是FastClickHelper文件的包名

package com.csii.doubleclick.fastclick; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; public class FastClickMethodVisitor extends AdviceAdapter { // FastClickHelper 类的内部名称**`**`**************修改这里⬇️ private static final String HELPER_CLASS = "com/csii/baseutil/FastClickHelper"; private final int viewParamIndex; protected FastClickMethodVisitor(MethodVisitor methodVisitor, int access, String name, String descriptor) { super(Opcodes.ASM9, methodVisitor, access, name, descriptor); this.viewParamIndex = findFirstViewParameterIndex(descriptor); } private int findFirstViewParameterIndex(String descriptor) { Type[] args = Type.getArgumentTypes(descriptor); for (int i = 0; i < args.length; i++) { if (args[i].getClassName().equals("android.view.View")) { return i; } } return 0; // fallback } @Override protected void onMethodEnter() { // 在方法开头插入: // if (FastClickHelper.isFastClick(view)) return; // 加载方法中 为view的下标 loadArg(viewParamIndex); // 调用静态方法 FastClickHelper.isFastClick(View) visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "isFastClick", "(Landroid/view/View;)Z", false); // 判断返回值 org.objectweb.asm.Label label = new org.objectweb.asm.Label(); // 如果 isFastClick == false, ifZCmp(EQ, label); // 否则直接返回(拦截点击) returnValue(); // 正常逻辑的标签 visitLabel(label); } public void returnValue() { visitInsn(Opcodes.RETURN); } }

必须 includeBuild
最后。在你的。settings.gradle 中
includeBuild(‘doubleClick’) doubleClick 是你的lib名称

ok 你的项目已经实现了全局的防抖

FastClickHelper.setDisabled(view, true) //这是禁止某个view插桩的调用方法

如果不想下载资源,一下是lib的源码 ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
build.gradle

buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.4.2' } } plugins { id 'java-gradle-plugin' } repositories { google() mavenCentral() } dependencies { // 注意:使用单引号,没有括号! compileOnly 'com.android.tools.build:gradle:7.4.2' implementation 'org.ow2.asm:asm:9.6' implementation 'org.ow2.asm:asm-commons:9.6' } gradlePlugin { plugins { create('doubleClick') { id = 'com.csii.doubleclick.fastclick' implementationClass = 'com.csii.doubleclick.fastclick.FastClickPlugin' } } } java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 }

com.csii.doubleclick.fastclick
FastClickClassVisitor.JAVA

package com.csii.doubleclick.fastclick; import com.android.build.api.instrumentation.ClassData; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; /*** * 可以根据 * 自己的需求 * 来进行插桩 */ public class FastClickClassVisitor extends ClassVisitor { private String className; public FastClickClassVisitor(ClassVisitor classVisitor) { super(Opcodes.ASM9, classVisitor); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); // 匹配目标方法名,且参数包含 View、返回 void if (("onClick".equals(name) || "onItemClick".equals(name) || name.contains("lambda$")) && descriptor.contains("Landroid/view/View;") && descriptor.endsWith(")V")) { System.out.println("[FastClick] Instrumenting: " + className + "." + name + descriptor); return new FastClickMethodVisitor(mv, access, name, descriptor); } return mv; } }

FastClickClassVisitorFactory.java

package com.csii.doubleclick.fastclick; 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.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.objectweb.asm.ClassVisitor; public abstract class FastClickClassVisitorFactory implements AsmClassVisitorFactory<FastClickClassVisitorFactory.Parameters> { public interface Parameters extends InstrumentationParameters { @Input Property<Boolean> getEnabled(); } @Override public ClassVisitor createClassVisitor(ClassContext classContext, ClassVisitor nextClassVisitor) { if (getParameters().get().getEnabled().get()) { return new FastClickClassVisitor(nextClassVisitor); } return nextClassVisitor; } @Override public boolean isInstrumentable(ClassData classData) { String className = classData.getClassName(); //这里根据自己的包名,过滤,我是只做我项目的过滤 if (className.startsWith("com.***.***") || className.startsWith("com.***.***")) { return true; } // 过滤掉系统类和不需要插桩的类 // if (className.startsWith("android.")) return false; // if (className.startsWith("androidx.")) return false; // if (className.startsWith("kotlin.")) return false; // if (className.contains("R$")) return false; // if (className.endsWith("R")) return false; // if (className.endsWith("BuildConfig")) return false; // if (className.contains("FastClickHelper")) return false; return false; } }

FastClickMethodVisitor。java

package com.csii.doubleclick.fastclick; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; public class FastClickMethodVisitor extends AdviceAdapter { // FastClickHelper 类的内部名称 private static final String HELPER_CLASS = "com/csii/baseutil/FastClickHelper"; private final int viewParamIndex; protected FastClickMethodVisitor(MethodVisitor methodVisitor, int access, String name, String descriptor) { super(Opcodes.ASM9, methodVisitor, access, name, descriptor); this.viewParamIndex = findFirstViewParameterIndex(descriptor); } private int findFirstViewParameterIndex(String descriptor) { Type[] args = Type.getArgumentTypes(descriptor); for (int i = 0; i < args.length; i++) { if (args[i].getClassName().equals("android.view.View")) { return i; } } return 0; // fallback } @Override protected void onMethodEnter() { // 在方法开头插入: // if (FastClickHelper.isFastClick(view)) return; // 加载第一个参数(View view) loadArg(viewParamIndex); // 调用静态方法 FastClickHelper.isFastClick(View) visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "isFastClick", "(Landroid/view/View;)Z", false); // 判断返回值 org.objectweb.asm.Label label = new org.objectweb.asm.Label(); // 如果 isFastClick == false,跳转到正常逻辑 ifZCmp(EQ, label); // 否则直接返回(拦截点击) returnValue(); // 正常逻辑的标签 visitLabel(label); } public void returnValue() { visitInsn(Opcodes.RETURN); } }

FastClickPlugin。java 插件类

package com.csii.doubleclick.fastclick; import com.android.build.api.instrumentation.FramesComputationMode; import com.android.build.api.instrumentation.InstrumentationParameters; import com.android.build.api.instrumentation.InstrumentationScope; import com.android.build.api.variant.AndroidComponentsExtension; import org.gradle.api.Plugin; import org.gradle.api.Project; import kotlin.Unit; public class FastClickPlugin implements Plugin<Project> { @Override public void apply(Project project) { AndroidComponentsExtension<?, ?, ?> androidComponents = project.getExtensions().findByType(AndroidComponentsExtension.class); if (androidComponents == null) { System.err.println("FastClickPlugin: AndroidComponentsExtension not found"); return; } // 为所有变体注册插桩 androidComponents.onVariants( androidComponents.selector().all(), variant -> { System.out.println("FastClickPlugin: instrumenting " + variant.getName()); variant.getInstrumentation().transformClassesWith( FastClickClassVisitorFactory.class, InstrumentationScope.ALL, params -> { params.getEnabled().set(true); return Unit.INSTANCE; } ); variant.getInstrumentation().setAsmFramesComputationMode( FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS ); } ); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 21:09:12

我优化了3000个测试用例后,终于学会放过自己

深夜的办公室&#xff0c;只有屏幕的微光和键盘敲击声。我看着文档里密密麻麻的、标记着“待优化”的三千个测试用例&#xff0c;第一次感到了窒息。这不是我第一次面对庞大的用例库&#xff0c;但这一次&#xff0c;我感到的不仅是疲惫&#xff0c;还有一种深层的无力——无论…

作者头像 李华
网站建设 2026/4/16 21:07:26

DownKyi哔哩下载姬:从视频获取到知识管理的完整能力图谱

DownKyi哔哩下载姬&#xff1a;从视频获取到知识管理的完整能力图谱 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等&…

作者头像 李华
网站建设 2026/4/16 21:07:26

相似度计算实战:Jaccard系数与简单匹配系数的应用对比

1. 从买菜到推荐系统&#xff1a;相似度计算的奇妙之旅 想象一下你去菜市场买菜的场景&#xff1a;摊位上摆着土豆、西红柿、黄瓜三种蔬菜。你买了土豆和西红柿&#xff0c;邻居买了西红柿和黄瓜。你们俩的"购物车相似度"该怎么计算&#xff1f;这就是Jaccard系数最擅…

作者头像 李华
网站建设 2026/4/16 21:06:58

NVIDIA Profile Inspector实用指南:三步轻松掌握显卡隐藏性能优化

NVIDIA Profile Inspector实用指南&#xff1a;三步轻松掌握显卡隐藏性能优化 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款功能强大的显卡驱动参数配置工具&#xff…

作者头像 李华
网站建设 2026/4/16 21:06:38

图像变化检测技术在军事毁伤评估中的实战应用解析

1. 图像变化检测技术如何成为军事毁伤评估的"火眼金睛" 第一次接触军事毁伤评估时&#xff0c;我完全没想到这个领域会如此依赖图像分析技术。直到亲眼看到某次演习的评估报告&#xff0c;才发现原来现代军事行动的效果验证&#xff0c;早已不是靠人工肉眼比对照片那…

作者头像 李华
网站建设 2026/4/16 21:03:54

2025年网盘下载太慢?8大网盘直链下载工具LinkSwift完整解决方案

2025年网盘下载太慢&#xff1f;8大网盘直链下载工具LinkSwift完整解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云…

作者头像 李华