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 ); } ); } }