如何避免连续点击或ListView下拉请求异步数据导致数据重复

 

编者:李国帅

qq:9611153 微信lgs9611153

时间:2019-02-20

背景原因:

在android编程的时候,经常会遇到连续点击或ListView下拉,激发事件后向服务器请求数据,等返回后进行数据处理的情况。

为了避免这种情况,一般想到的就是计算两次事件的时间间隔,第二次激发事件的时候直接跳过。网上实现这种方法的方式很多,不过万变不离其宗。类似如下:

如何避免连续点击或ListView下拉请求异步数据导致数据重复_第1张图片

         这种方法并不是完美的方案,弊端有很多:

1,两次事件之间的间隔多少才算合适呢?不同的情景都不一样,使用统一的时间间隔明显不合适。

2,如果事件激发后需要向后台请求数据,那么网络的好坏和服务器响应时间都会成为制约因素。

3、有的解决方法使用全局静态的时间间隔,那么就会造成所有的事件变成的相关事件,如果两个事件无关而被快速激发的时候,就会造成后面的事件无法执行,从而导致业务逻辑问题。

4、对于ListView RecyclerView这样的控件一次下拉可能多次激发”加载更多”事件,如果网络迟延大于事件间隔,有可能导致重复从同一位置请求的事件。

所需资源:

Android studio

 

个人建议:

故而,我认为判断是否激发第二次事件,应该根据第一次激发事件的结果而判断。

如果是普通的二次激发,可以直接丢弃;如果是允许的二次激发,那么应该根据第一次的事件响应来判断。

比如listview的下拉事件,明显是可以进行快速重复调用加载事件的。

这就需要添加一个判断标记,当响应之后恢复标记表示可以重新开始激发事件。

示意如下:

    //是否正在获取数据
    private boolean isLoadingData = false;
    初始调用
      isLoadingData = false;
      loadData(curPos);
	需要的时候调用
       loadData(curPos);
	 调用之前需要先进行判断
   private void loadData(int iPos) {
         if (onGetDataListener != null && !isLoadingData) {
            isLoadingData = true;
            onGetDataListener.getData(iPos);//真正调用接口获取网络数据
        } else {
            SxbLog.e("listAdapter","loadData isLoadingData == " + isLoadingData);
        }
    }

	public void afterGetData() {//调用结束后
		//设置数据
		//列表中信息数量增加,curPos改变
		isLoadingData = false;
	}

   

 

方案1

下面列举一个曾经使用过的方案。

 

1、使用简单变量,控制多次调用retrofit调用

	private boolean isAdding = false;// 正在添加

	private void addData() {
		if (isAdding) {
			return;
		}
		isAdding = true;
		//....
		JsonDataCheck.printJsonObj(1, in);
		MyServiceI service = MyApplication.getServiceI(MyServiceI.WAITTIMEOUT_SENCONDS);
		Call model = service.addData(in);//通过retrofit2传统方法获取数据
		model.enqueue(new Callback() {
			public void onResponse(Call call, Response response) {
				//数据处理
				isAdding = false;
		}

 

 

 

2、使用锁定类改造

public class ServiceOperate {
//...
	//避免多次操作
	private static Integer taskRefreshing = 0;
	public static void setLock(int value)
	{
		synchronized (taskRefreshing) {
			taskRefreshing = value;
		}
	}
	public static boolean isLock()
	{
		synchronized (taskRefreshing) {
			if (taskRefreshing == 1) {

				return true;
			}
		}
		return false;
	}
}

打开页面的时候初始化

protected void onCreate(Bundle savedInstanceState) {

        ServiceOperate.setLock(0);//避免以前被锁定

 

                  

异步查询

private void addData(final int newStatus) {
 //...
        if (ServiceOperate.isLock()) {//查询之前判断是否正在查询
            return;
        }
        ServiceOperate.setLock(1);//锁定
 //...
        MyServiceI service = MyApplication.getServiceI(MyServiceI.WAITTIMEOUT_SENCONDS);
        Call model = service.addData(in);
        model.enqueue(new Callback() {
            public void onResponse(Call call, Response response) {
 				//数据处理
               ServiceOperate.setLock(0);//查询后解锁
		}

 

3、使用list中的变量,不在外部使用锁定。

可以把锁定放在xlistview的setDataUpdating函数中

	@SuppressLint("HandlerLeak")
	private final Handler mHandler = new Handler();

	class ixListListener implements IXListViewListener {
		@Override
		public void onRefresh() {
			mHandler.postDelayed(new Runnable() {
				@Override
				public void run() {
					if (!mListView.getDataUpdating()) {
						mListView.setDataUpdating(true);//查询的时候设置
						refreshContent();
					}
				}
			}, 50);
		}

		@Override
		public void onLoadMore() {
			mHandler.postDelayed(new Runnable() {
				@Override
				public void run() {
					if (!mListView.getDataUpdating()) {
						mListView.setDataUpdating(true);
						updateList();// 追加数据。
					}
				}
			}, 50);
		}
	}

 

         查询完成后复原

		private void updateList() {

		if (readTag == 2) {
			refreshListView();//条件不具备复原
			return;// 数据添加完毕
		}

//...
		JsonDataCheck.printJsonObj(1, in);
		MyServiceI service = MyApplication.getServiceI(MyServiceI.WAITTIMEOUT_SENCONDS);
		Call model = service.QueryList(in);
		model.enqueue(new Callback() {
			public void onResponse(Call call, Response response) {
				refreshListView();//查询后复原,间接调用mListView.setDataUpdating(false);
				

当然了,这里只是一个简单的没有太多花哨的技术。

你可能感兴趣的:(android,避免多次点击)