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
                    );
                }
        );
    }
}
复制代码
相关推荐
小白学大数据2 小时前
Python 爬取图片攻略:告别水印,批量保存高清图片
开发语言·python
程序员陆业聪2 小时前
Android图片加载框架深度对比:Coil 3.4.0 vs Glide 5.0,该选哪个?
android
seabirdssss2 小时前
Android 模拟器搭建
android·经验分享
lhbian2 小时前
30分钟搭建PHP+Java全栈Web应用
java·前端·php
有谁看见我的剑了?2 小时前
Linux 内存巨页与透明巨页学习
java·linux·学习
勿忘,瞬间2 小时前
Spring Boot
java·数据库·spring boot
sycmancia2 小时前
Qt——应用程序中的主窗口
开发语言·qt
weixin_471383032 小时前
[特殊字符] React Flow 从入门到理解
开发语言·前端·javascript
SimonKing2 小时前
AI大模型中转平台,无需科学上网就可以使用国外模型
java·后端·程序员