背景需求
在用户提交表单内容,请求接口的通用场景下,增删改查 crud 操作是比较频繁出现的操作。
对于改查操作来讲,并不会引起数据上的问题,影响的最多是接口重复调用;但是对于增删操作来讲,很容易引起数据重复添加,删除异常等问题。
对用户来讲,网络延时或者不稳定造成请求接口时出现的App反应迟缓,下意识的会进行重复提交;或者用户不小心进行的重复点击;又或者测试App自动化测试工具导致的短时间内大量重复点击。
这些场景都应该在代码实现的时候应该考虑的。
1. “笨”方法实现
说是笨方法,当然也是出现重复提交问题时候,想到的最有效的方法。
1.1 状态标记
对于每个点击按钮,初始状态 isPressed = FALSE 进行状态判定,点击一次记录状态 isPressed = TURE ,然后进行接口请求或者其它处理,接口请求完成之后初始化该按钮状态 isPressed = FALSE 。
1.2 状态标记高级版
Android 中怎样设置按钮不能点击?
其实,和上面的方法没什么区别,只不过借助了** Button **的属性设置来完成的。
Button.setEnabled(false);//设置这个属性
思路和1.1一致,生了一个局部变量,依然有效!
2. Clever方法实现
其实,对于重复点击这类问题,只需要一个状态标记就能够解决,但是我们的之前的方法只适用于单个** Button **按钮,对于多个按钮,如果我们这么去做,必然是大量的 Ctrl+C 和 Ctrl+V,想想都心累,更别说我还真的这么干过。
如果有一种一劳永逸的方法岂不是很好,对了,然后我们想到了封装,从** Button 组件内部去判断岂不是美妙,这样我们只需要替换xml中的 Button 就可以完美的解决大量的按钮点击事件。
既然, Button **按钮的事件点击是通过
setOnClickListener(OnClickListener listener)
方法注册的,我们只需要在注册的时候,替换新的Listener,加上我们自定义事件判断就可以搞定。
比如:
public class OnNoRepeatButton extends Button
{
public ButtonEx(final Context context)
{
super(context);
}
public ButtonEx(final Context context, final AttributeSet attrs)
{
super(context, attrs);
}
public ButtonEx(final Context context, final AttributeSet attrs, final int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
@Override
public void setOnClickListener(final OnClickListener listener)
{
super.setOnClickListener(new OnNoRepeatClickListener(listener));
}
}
我们的** OnNoRepeatClickListener **:
/**
* 防止快速点击中多次触发事件的自定义OnClickListener
*/
public class OnNoRepeatClickListener implements OnClickListener
{
private final OnClickListener mListener;
private long lastClickTime = 0;
public OnNoRepeatClickListener(final OnClickListener listener)
{
this.mListener = listener;
}
@Override
public void onClick(final View v)
{
synchronized (this)
{
//判断当前点击时间与上一次点击时间的间隔
if (System.currentTimeMillis() - this.lastClickTime > 1000)
{
if (this.mListener != null)
this.mListener.onClick(v);
//处理完响应事件后记录时间确保下次响应
this.lastClickTime = System.currentTimeMillis();
}
}
}
}
Note: 代码中的** 1000 **单位是ms,具体的时间需要根据需求来定义。
3. Smart方法实现
Clever方法已经算是一个相对好的解决思路,但是如果我们的** Textview ,ImageView , View **等组件点击事件重复需要怎么解决呢?
也需要我们重写每一个的组件的
setOnClickListener(OnClickListener listener)
来实现。
那么,如果我们在重写了** Activity/Fragment **中重写
View.OnClickListener
方法,对点击事件时间进行判断,便可以减少对** View **组件的
setOnClickListener(OnClickListener listener)
方法的重写。
上代码:
interface IBaseView extends View.OnClickListener {
/**
* 初始化数据
*
* @param bundle 传递过来的bundle
*/
void initData(final Bundle bundle);
/**
* 绑定布局
*
* @return 布局Id
*/
int bindLayout();
/**
* 初始化view
*/
void initView(final Bundle savedInstanceState, final View view);
/**
* 业务操作
*/
void doBusiness();
/**
* 视图点击事件
*
* @param view 视图
*/
void onWidgetClick(final View view);
}
BaseActivity.java
/**
* <pre>
* desc : Activity基类
* </pre>
*/
public abstract class BaseActivity extends AppCompatActivity
implements IBaseView {
/**
* 当前Activity渲染的视图View
*/
protected View contentView;
/**
* 上次点击时间
*/
private long lastClick = 0;
protected BaseActivity mActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = this;
Bundle bundle = getIntent().getExtras();
initData(bundle);
setBaseView(bindLayout());
initView(savedInstanceState, contentView);
doBusiness();
}
protected void setBaseView(@LayoutRes int layoutId) {
setContentView(contentView = LayoutInflater.from(this).inflate(layoutId, null));
}
/**
* 判断是否快速点击
*
* @return {@code true}: 是<br>{@code false}: 否
*/
private boolean isFastClick() {
long now = System.currentTimeMillis();
if (now - lastClick >= 800) {
lastClick = now;
return false;
}
return true;
}
@Override
public void onClick(final View view) {
if (!isFastClick()) onWidgetClick(view);
}
}
总结
当然,Smart方法也不是最佳的解决方案,这样来做需要组件采用
setOnClickListener(this)
调用方式,让** Activity/Fragment **统一处理点击事件的响应,Clever和Smart的结合也是一个不错的选择。
Have fun , enjoy Coding...