当前位置: 首页 > news >正文

Android深思如何防止快速点击

前言

其实快速点击是个很好解决的问题,但是如何优雅的去解决确是一个难题,本文主要是记录一些本人通过解决快速点击的过程中脑海里浮现的一些对这个问题的深思。

作者:流浪汉kylin 链接:https://juejin.cn/post/7197337416096055351

1. AOP

可以通过AOP来解决这个问题,而且AOP解决的方法也很优雅,在开源上也应该是能找到对应的成熟框架。

AOP来解决这类问题其实是近些年一个比较好的思路,包括比如像数据打点,通过AOP去处理,也能得到一个比较优雅的效果。牛逼的人甚至可以不用别人写的框架,自己去封装就行,我因为对这个技术栈不熟,这里就不献丑了。
总之,如果你想快速又简单的处理这种问题,AOP是一个很好的方案

2. kotlin

使用kotlin的朋友有福了,kotlin中有个概念是扩展函数,使用扩展函数去封装放快速点击的操作逻辑,也能很快的实现这个效果。它的好处就是突出两个字“方便”

那是不是我用java,不用kotlin就实现不了kotlin这个扩展函数的效果?当然不是了。这让我想到一件事,我也有去看这类问题的文章,看看有没有哪个大神有比较好的思路,然后我注意到有人就说用扩展函数就行,不用这么麻烦。

OK,那扩展函数是什么?它的原理是什么?不就是静态类去套一层吗?那用java当然能实现,为什么别人用java去封装这套逻辑就是麻烦呢?代码不都是一样,只不过kotlin帮你做了而已。所以我觉得kotlin的扩展函数效果是方便,但从整体的解决思路上看,缺少点优雅。

3. 流

简单来说也有很多人用了Rxjava或者kotlin的flow去实现,像这种实现也就是能方便而已,在底层上并没有什么实质性的突破,所以就不多说了,说白了就是和上面一样。

4. 通过拦截

因为上面已经说了kt的情况,所以接下来的相关代码都会用java来实现。
通过拦截来达到防止快速点击的效果,而拦截我想到有2种方式,第一种是拦截事件,就是基于事件分发机制去实现,第二种是拦截方法。
相对而言,其实我觉得拦截方法会更加安全,举个场景,假如你有个页面,然后页面正在到计算,到计算完之后会显示一个按钮,点击后弹出一个对话框。然后过了许久,改需求了,改成到计算完之后自动弹出对话框。但是你之前的点击按钮弹出对话框的操作还需要保留。那就会有可能因为某些操作导致到计算完的一瞬间先显示按钮,这时你以迅雷不及掩耳的速度点它,那就弹出两次对话框。

(1)拦截事件

其实就是给事件加个判断,判断两次点击的时间如果在某个范围就不触发,这可能是大部分人会用的方式。

正常情况下我们是无法去入侵事件分发机制的,只能使用它提供的方法去操作,比如我们没办法在外部影响dispatchTouchEvent这些方法。当然不正常的情况下也许可以,你可以尝试往hook的方向去思考能不能实现,我这边就不思考这种情况了。

public class FastClickHelper {
    private static long beforeTime = 0;    private static Map<View, View.OnClickListener> map = new HashMap<>();
    public static void setOnClickListener(View view, View.OnClickListener onClickListener) {        map.put(view, onClickListener);        view.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                long clickTime = SystemClock.elapsedRealtime();                if (beforeTime != 0 && clickTime - beforeTime < 1000) {                    return;                }                beforeTime = clickTime;
                View.OnClickListener relListener = map.get(v);                if (relListener != null) {                    relListener.onClick(v);                }            }        });    }
}
 

简单来写就是这样,其实这个就和上面说的kt的扩展函数差不多。调用的时候就

FastClickHelper.setOnClickListener(view, this);

但是能看出这个只是针对单个view去配置,如果我们想其实页面所有view都要放快速点击,只不过某个view需要快速点击,比如抢东西类型的,那肯定不能防。所以给每个view单独去配置就很麻烦,没关系,我们可以优化一下

public class FastClickHelper {
    private Map<View, Integer> map;    private HandlerThread mThread;
    public void init(ViewGroup viewGroup) {        map = new ConcurrentHashMap<>();        initThread();        loopAddView(viewGroup);
        for (View v : map.keySet()) {            v.setOnTouchListener(new View.OnTouchListener() {                @Override                public boolean onTouch(View v, MotionEvent event) {                    if (event.getAction() == MotionEvent.ACTION_DOWN) {                        int state = map.get(v);                        if (state == 1) {                            return true;                        } else {                            map.put(v, 1);                            block(v);                        }                    }                    return false;                }            });        }    }
    private void initThread() {        mThread = new HandlerThread("LAZY_CLOCK");        mThread.start();    }
    private void block(View v) {        // 切条线程处理        Handler handler = new Handler(mThread.getLooper());        handler.postDelayed(new Runnable() {            @Override            public void run() {                if (map != null) {                    map.put(v, 0);                }            }        }, 1000);    }
    private void exclude(View... views) {        for (View view : views) {            map.remove(view);        }    }
    private void loopAddView(ViewGroup viewGroup) {        for (int i = 0; i < viewGroup.getChildCount(); i++) {            if (viewGroup.getChildAt(i) instanceof ViewGroup) {                ViewGroup vg = (ViewGroup) viewGroup.getChildAt(i);                map.put(vg, 0);                loopAddView(vg);            } else {                map.put(viewGroup.getChildAt(i), 0);            }        }    }
    public void onDestroy() {        try {            map.clear();            map = null;            mThread.interrupt();        } catch (Exception e) {            e.printStackTrace();        }    }
}

我把viewgroup当成入参,然后给它的所有子view都设置,因为onclicklistener比较常用,所以改成了设置setOnTouchListener,当然外部如果给view设置了setOnTouchListener去覆盖我这的set,那就只能自己做特殊处理了。

在外部直接调用

FastClickHelper fastClickHelper = new FastClickHelper();fastClickHelper.init((ViewGroup) getWindow().getDecorView());

如果要想让某个view不要限制快速点击的话,就调用exclude方法。这里要注意使用完之后释放资源,要调用onDestroy方法释放资源。

关于这个部分的思考,其实上面的大家都会,也基本是这样去限制,但是就是即便我用第二种代码,也要每个页面都调用一次,而且看起来,多少差点优雅。

首先我想的办法是在事件分发下发的过程去做处理,就是在viewgroup的dispatchTouchEvent或者onInterceptTouchEvent这类方法里面,但是我简单看了源码是没有提供方法出来的,也没有比较好去hook的地方,所以只能暂时放弃思考在这个下发流程去做手脚。

补充一下,如果你是自定义view,那肯定不会烦恼这个问题,但是你总不能所有的view都做成自定义的吧。

其次我想怎么能通过不写逻辑代码能实现这个效果,但总觉得这个方向不就是AOP吗,或者不是通过开发层面,在开发结束后想办法去注入字节码等操作,我觉得要往这个方向思考的话,最终的实现肯定不是代码层面去实现的。

(2)拦截方法

上面也说了,相对于拦截事件,假设如果都能实现的情况下,我更倾向于去拦截方法。

因为从这层面上来说,如果实现拦截方法,或者说能实现中断方法,那就不只是能做到防快速点击,而是能给方法去定制相对应的规则,比如某个方法在1秒的间隔内只能调用一次,这个就是防快速点击的效果嘛,比如某个方法我限制只能调一次,如果能实现,我就不用再额外写判断这个方法调用一次过后我设置一个布尔类型,然后下次调用再判断这个布尔类型来决定是否调用,

那现在是没办法实现拦截方法吗?当然有办法,只不过会十分的不优雅,比如一个方法是这样的。

public void fun(){    // todo 第1步    // todo 第2步    // todo ......    // todo 第n步}

那我可以封装一个类,里面去封装一些策略,然后根据策略再去决定方法要不要执行这些步骤,那可能就会写成

public void fun(){    new FunctionStrategy(FunctionStrategy.ONLY_ONE, new CallBack{        @Override        public void onAction() {            // todo 第1步            // todo 第2步            // todo ......            // todo 第n步        }    })}

这样就实现了,比如只调用一次,具体的只调用一次的逻辑就写在FunctionStrategy里面,然后第2次,第n次就不会回调。当然我这是随便乱下来表达这个思路,现实肯定不能这样写。首先这样写就很不优雅,其次也会存在很多问题,扩展性也很差。

那在代码层面还有其它办法拦截或者中断方法吗,在代码层还真有办法中断方法,没错,那就是抛异常,但是话说回来,你也不可能在每个地方都try-catch吧,不切实际。

目前对拦截方法或者中断方法,我是没想到什么好的思路了,但是我觉得如果能实现,对防止快速点击来说,肯定会是一个很好的方案。

相关文章:

Android深思如何防止快速点击

前言 其实快速点击是个很好解决的问题&#xff0c;但是如何优雅的去解决确是一个难题&#xff0c;本文主要是记录一些本人通过解决快速点击的过程中脑海里浮现的一些对这个问题的深思。 作者&#xff1a;流浪汉kylin 链接&#xff1a;https://juejin.cn/post/7197337416096055…...

PHP自己的框架cookie()使用(完善篇七)

1、PHP自己的框架cookie() 2、cookie类&#xff08;CookieBase.php&#xff09; <?php class CookieBase {/*** 设置cookie*/public static function set($name, $value, $expire 3600, $path , $domain , $secure false, $httponly false) {setcookie($name, $valu…...

Spring Boot Dubbo Zookeeper(含ZK安装脚本)

文章目录 Spring Boot Dubbo Zookeeper&#xff08;含ZK安装脚本&#xff09;简介DubboCommonProviderConsumer Zookeeper Spring Boot Dubbo Zookeeper&#xff08;含ZK安装脚本&#xff09; 简介 Dubbo Common 公共依赖 <!-- Spring Boot Starter --> <dependen…...

BigDecimal百科全书

一、BigDecimal简述 Java在java.math包中提供的API类BigDecimal&#xff0c;用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数&#xff0c;但在实际应用中&#xff0c;可能需要对更大或者更小的数进行运算和处理。 一般情况下&#xff0c;…...

【30天熟悉Go语言】11 数组的全方位使用与解析

作者&#xff1a;秃秃爱健身&#xff0c;多平台博客专家&#xff0c;某大厂后端开发&#xff0c;个人IP起于源码分析文章 &#x1f60b;。 源码系列专栏&#xff1a;Spring MVC源码系列、Spring Boot源码系列、SpringCloud源码系列&#xff08;含&#xff1a;Ribbon、Feign&…...

静态路由(详细理解+实例精讲)

系列文章目录 华为数通学习&#xff08;6&#xff09; 前言 一&#xff0c;静态路由 二&#xff0c;静态路由配置 三&#xff0c;缺省路由 四&#xff0c;缺省路由应用场景 总结 前言 随着华为公司的不断发展&#xff0c;数据通信这门技术也越来越重要&#xff0c;很多人…...

leetcode做题笔记118. 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 思路一&#xff1a;动态规划 int** generate(int numRows, int* returnSize, int** returnColumnSizes){int **returnnum(int **)…...

stm32之24.RTC闹钟usart端口修改配置

&#xff08;需要修改&#xff09; 源码 while(1){//rtc唤醒事件if(g_rtc_wakeup_event){//获取日期RTC_GetDate(RTC_Format_BCD,&RTC_DateStructure);printf("20%02x/%02x/%02xWeek:%x\r\n",RTC_DateStructure.RTC_Year,RTC_DateStructure.RTC_Month,RTC_Date…...

Spring Security无法调用接口错误解决

之前在写程序的时候&#xff0c;发现有个接口使用postmapping发送请求一直无法进行调用 PostMapping("/user/login")public ResponseResult login(User user){//登录&#xff0c;这里登录的时候需要传入用户名和密码System.out.println("user "user.toSt…...

运维Shell脚本小试牛刀(二)

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 一: if---else.....fi 条件判断演示 [rootwww shelldic]# cat checkpass.sh #!/bin/bash - # # # # FILE: ch…...

飞天使-python的模块与包与装饰器

文章目录 模块与包标准模块第三方模块自定义模块 高级语法切片迭代器/生成器高级模式&#xff08;闭包&#xff09;高级模式&#xff08;装饰器&#xff09; 参考视频 模块与包 标准模块 import os print(os.getcwd())import sys print(sys.argv) print(sys.platform) print(…...

linux shell脚本利用 kill -0 检查进程是否存在

1.kill -0介绍 kill -0 pid用来检查进程是否存在&#xff0c;kill -0 pid解释(来自man kill)&#xff1a;“If sig is 0, then no signal is sent, but error checking is still performed.”不发送任何信号&#xff0c;但是系统会进行错误检查。 kill -0 pid ->若存在&am…...

抖音视频删了怎么在电脑上找回来

【昨天整理电脑文件时&#xff0c;不小心将剪辑好的抖音作品误删了&#xff0c;但是回收站中找不回来了&#xff0c;这些视频是我花了很多心血制作的&#xff0c;如果没了真的十分可惜&#xff01;希望大家能帮帮我&#xff0c;告诉我应该如何恢复这些文件。】 现在人们都喜欢…...

方面级别情感分析之四元组预测

情感四元组预测现有方法 阅读本文之前我们默认你对情感分析有基本的认识。 如果没有请阅读文章(https://tech.tcl.com/post/646efb5b4ba0e7a6a2da6476) 情感分析四元组预测涉及四个情感元素: 方面术语a&#xff0c;意见术语(也叫观点术语)o&#xff0c; 方面类别ac&#xff0c…...

算法 稀疏数组 数组优化 数组压缩 二维数组转稀疏数组 算法合集(二)

1. 五子棋游戏&#xff0c;玩家对战一半停战休息&#xff0c;此时需要存储当前对战双方棋子信息 a. 采用二维数组存储&#xff1a; 0为空&#xff0c; 1代表黑棋 2代表蓝色棋子 b. 棋盘为11行&#xff0c;11列 > int [][] chessArray new int [11][11]; c. 出现的问题&am…...

交换机端口安全实验

文章目录 一、实验的背景与目的二、实验拓扑三、实验需求四、实验解法1. PC配置IP地址部分2. 在SW1上开启802.1X身份验证3. 创建一个用户身份验证的用户。用户名为wangdaye&#xff0c;密码为1234564.创建一个端口隔离组&#xff0c;实现三台PC无法互相访问 摘要&#xff1a; 本…...

c# 本地化中英文切换

区域 线程默认区域为当前计算机所选区域 设置当前区域&#xff1a; Thread.CurrentThread.CurrentCulture new CultureInfo(“zh-cn”); 获取当前区域&#xff1a; Console.WriteLine(Thread.CurrentThread.CurrentCulture.ToString()); 区域名称&#xff1a; “zh-cn” 中文…...

rabbitmq的优先级队列

在我们系统中有一个 订单催付 的场景&#xff0c;我们的客户在天猫下的订单 , 淘宝会及时将订单推送给我们&#xff0c;如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒&#xff0c;很简单的一个功能对吧&#xff0c;但是&#xff0c;tianmao商家对我们来说&#…...

SpringBoot的Cacheable缓存注解

当我们的应用程序需要频繁地读取和写入数据时&#xff0c;为了提高应用程序的性能&#xff0c;我们通常会使用缓存技术。Spring Boot 提供了一种简单而强大的缓存框架&#xff0c;它可以轻松地将数据缓存到 Redis 中。 在 Spring Boot 中可以在方法上简单的加上注解实现缓存。…...

uniapp的 picker 日期时间选择器

效果图&#xff1a; dateTimePicker.js function withData(param){return param < 10 ? 0 param : param; } function getLoopArray(start,end){var start start || 0;var end end || 1;var array [];for (var i start; i < end; i) {array.push(withData(i))…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...