Android 按钮点击频率控制设计

在android 开发中,我们经常需要控制按钮的点击频率,以及多次重复点击问题。比如点击了提交按钮后,我们期望用户只点一次,并等待我们网络请求返回后才能再次点击有效点击。
但用户似乎永远都在跟我们对着干,他可能正处在生气暴发的边缘,可能正在无聊的不停点着同一个按钮。于是我们发现自己的服务器中充斥着重复的错误数据。
这个问题的另一个极端的例子是,当我们点击一下按钮,发送了一个网络请求用于处理用户 信息,如果用户暴力的使用精灵类自动点击工具,那么有可能一秒钟过后(假如一秒钟可以生成100次点击事件),程序会因同时提交的网络请求数过多而直接崩溃,即使程序没有崩溃,后端的服务也将会有被暴力攻陷的危险。这是我们不希望的。

现在需要这样一种机制,能够防止用户错误的快速碰触多次提交按钮,能够控制用户在多长时间内不能再次点击按钮,或者点击按钮并不触发真正的业务逻辑,再牛逼一点我们可能还需要一种控制逻辑让这个按钮一直不可点击,直到某一个异步任务执行完毕。

根据要求我们总结以下需求:
1. 防止用户多次重复提交数据。
2. 在500ms内点连续击按钮不触发相应业务。
3. 特殊的业务要求能够控制异步任务执行完成后才能再次触发业务逻辑。

于是有人想到了这样的方法:

    private boolean isSubmitClicked = false;
    public void onClick(View view){
        switch(view.getId()){
        case R.id.submitBtn:{
            if(isSubmitClicked){return;}
            isSubmitClicked = true;
            doSomethingReal();
            isSubmitClicked = false;
        }
        }
    }

这种设计是否满足要求暂且不说,当我们界面上有数十个按钮都需要这样的控制时,就显能完全不能胜任,最明显的缺点是控制逻辑与业务逻辑耦合太紧密,其次是需要一堆的控制变量声明。
我们需要一种分离的控制结构,同时能够满足以上3点要求。于是有了如下的代码:

package cn.andrewlu.app;

import java.util.HashMap;
import java.util.Map;

public final class ButtonSlop {
    // 用来记录所有的按钮最后点击时间。
    private final static Map<String, Long> SLOPS_MAP = new HashMap<String, Long>();
    private static int MIN_SLOP = 500;

    /**
     * 默认500ms内不能再次点击。
     * 
     * @param buttonId
     *            一般可以使用view.getId()用来区分不同的按钮。
     * @return true=表示当前不能点击,一般可以弹出提示,你点太快了,同时需要退出onClick。 false =
     *         表示可以再次触发点击了,一般放行,就可以进行处理了。
     */
    public static boolean check(int buttonId) {
        return check(buttonId, MIN_SLOP);
    }
    /**
     * 
     * @param buttonId
     * @param holdTimeMills  传入最少需要等待的时间。如果不确定,可以调用上一个函数。默认最少500ms.
     * @return
     */
    public static boolean check(int buttonId, int holdTimeMills) {
        return check(String.valueOf(buttonId), holdTimeMills);
    }

    public static boolean waitInfinte(int buttonId) {
        return waitInfinte(String.valueOf(buttonId));
    }

    public static void cancel(int buttonId) {
        cancel(String.valueOf(buttonId));
    }

    public static boolean check(String buttonTag, int holdTimeMills) {
        if (buttonTag == null || buttonTag.length() <= 0)
            return true;// 合理的方式是确保参数不能为空,此处应该抛异常才合理。
        if (holdTimeMills < 100) {// 时间太短,没有意义。
            holdTimeMills = 100;
        }
        // 用同步块的方式控制防止多线程中操作失误。
        synchronized (SLOPS_MAP) {
            Long lastTipLong = SLOPS_MAP.get(buttonTag);
            if (lastTipLong == null
                    || System.currentTimeMillis() - lastTipLong >= holdTimeMills) {
                SLOPS_MAP.put(buttonTag, System.currentTimeMillis());
                return false;// 表示第一次点击 或者 两次之间点击差够长了。
            } else {
                // 时间太短不允许再次触发。
                return true;
            }
        }
    }

    public static boolean waitInfinte(String buttonTag) {
        synchronized (SLOPS_MAP) {
            Long lastTipLong = SLOPS_MAP.put(buttonTag,
                    System.currentTimeMillis());
            return lastTipLong != null; // !=null说明已经存储过一次点击事件了,需要调用cancel后才能再次点击。
        }
    }

    public static void cancel(String buttonTag) {
        synchronized (SLOPS_MAP) {
            SLOPS_MAP.remove(buttonTag);
        }
    }
}

以上代码为静态最终类,提交了7个函数用来控制逻辑,实际只有两个真正的业务控制:check(), cancel()。
chcek()函数用来检测本次点击是否有效,有效的话会返回false.(当然按照函数功能描述,有效应该返回true才对,但是这里我不准备返回true.这个看后边代码就会明白。)
而cancel()函数则用来取消按钮控制。典型的场景是:在网络请求成功后调用,以便允许用户能再次点击提交信息(虽然再次点击的业务并不恰当,但仅仅控制用户无法过度点击还是很有效的。)

控制业务将会变成如下样式:

@Override
    public void onClick(View arg0) {
        // TODO Auto-generated method stub
        switch (arg0.getId()) {
        case R.id.button: {
            // 这是必要的,要让用户 知道他点太快了。
            if (ButtonSlop.waitInfinte(R.id.button)) {
                Toast.makeText(this, "男人不能太着急哈", Toast.LENGTH_SHORT).show();
                return;
            }
            //异步任务。需要调用cancel()才能再次触发以下业务。
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(15000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 任务完成后,恢复按钮点击事件。
                    ButtonSlop.cancel(R.id.button);
                }
            }.start();
        }
            break;
        case R.id.normalButton:{
            // 默认500ms后才能再次点击.
        if (ButtonSlop.check(R.id.normalButton)) {
                Toast.makeText(this, "男人不能太着急哈", Toast.LENGTH_SHORT).show();
                return;
            }
            //doSomething();
        }
        default:
            break;
        }
    }

以上代码有点类似于锁机制。当一段时间内锁还没有释放,刚直接返回不再继续触发业务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值