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