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

    技术2026-02-04  6

     

    编者:李国帅

    qq:9611153 微信lgs9611153

    时间:2019-02-20

    背景原因:

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

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

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

    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<AddDataOut> model = service.addData(in);//通过retrofit2传统方法获取数据 model.enqueue(new Callback<AddDataOut>() { public void onResponse(Call<AddDataOut> call, Response<AddDataOut> 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<AddDataOut> model = service.addData(in); model.enqueue(new Callback<AddDataOut>() { public void onResponse(Call<AddDataOut> call, Response<AddDataOut> 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<QueryListOut> model = service.QueryList(in); model.enqueue(new Callback<QueryListOut>() { public void onResponse(Call<QueryListOut> call, Response<QueryListOut> response) { refreshListView();//查询后复原,间接调用mListView.setDataUpdating(false);

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

    Processed: 0.018, SQL: 9