Android中RecyclerView子项调用API时加载异常该如何修复
问题修复方案
根因分析
- RecyclerView子项复用冲突:滚动列表时Holder会被复用绑定新数据,之前发起的异步API请求返回时,Holder已经对应其他子项,会错误更新UI
- 绑定逻辑缺陷:已经加载过积分的
phpoint类型卡片会进入else分支,错误调用getCardPoint拉取不匹配的积分数据 - 静态position失效:API异步返回期间如果列表有增删、刷新操作,传入的position早已不对应原数据项,会读取错误数据
- 无用请求未取消:Holder被复用或销毁时,未完成的请求没有取消,返回后仍会执行更新,导致数据错乱
修复步骤
1. 修正onBindViewHolder逻辑
已经加载过积分的子项直接读取缓存渲染,不要重复发起请求,每次绑定新数据前先取消Holder上未完成的旧请求,重置UI状态。
@Override public void onBindViewHolder(@NonNull CustomerCardHolder holder, int position, @NonNull List<Object> payloads) { super.onBindViewHolder(holder, position, payloads); CustomerCardsResultObject currentCard = getList().get(position); // 取消上一个未完成的请求,避免复用冲突 holder.cancelOngoingRequest(); // 重置加载状态UI holder.shimmer.setVisibility(View.VISIBLE); holder.shimmer.startShimmer(); holder.puan.setText(""); // 已加载过积分的直接渲染缓存,不发起请求 if (currentCard.isCardPointLoaded()) { holder.shimmer.stopShimmer(); holder.shimmer.setVisibility(View.GONE); DecimalFormat df = new DecimalFormat("0.00"); holder.puan.setText(df.format(currentCard.getCardPoint())); return; } // 未加载的按类型发起对应请求 if (currentCard.getEnumTypeName().equals("phpoint")) { holder.getPhPoint(currentCard); } else { holder.getCardPoint(currentCard); } }
2. 给ViewHolder增加请求校验和取消逻辑
在ViewHolder中记录当前绑定的卡片ID和正在执行的请求,回调返回时先校验卡片ID是否匹配,不匹配直接丢弃结果。
public class CustomerCardHolder extends RecyclerView.ViewHolder { // 新增成员变量:当前绑定的卡片ID、正在执行的请求实例 private String currentBindCardId; private Call<?> ongoingApiCall; // 原有View声明省略 TextView puan; ShimmerFrameLayout shimmer; // 新增方法:取消未完成的请求 public void cancelOngoingRequest() { if (ongoingApiCall != null && !ongoingApiCall.isCanceled()) { ongoingApiCall.cancel(); } ongoingApiCall = null; currentBindCardId = null; } // 修改后的getPhPoint方法,不再传position public void getPhPoint(CustomerCardsResultObject targetCard) { currentBindCardId = targetCard.getId(); // 这里假设viewModel的方法返回Call实例,根据你自己的网络框架调整,RxJava就存Disposable,协程就存Job ongoingApiCall = viewModelCustomer.getCustomerPhPoint((customerPhPointResponse, success, errorBody) -> { // 校验当前Holder绑定的卡片是否是请求的卡片,不匹配直接丢弃结果 if (!targetCard.getId().equals(currentBindCardId)) { return; } shimmer.stopShimmer(); shimmer.setVisibility(View.GONE); if (success) { targetCard.setCardPointLoaded(true); DecimalFormat df = new DecimalFormat("0.00"); if (customerPhPointResponse.getResultObject() != null && customerPhPointResponse.getResultObject().getPhPoint() != null && customerPhPointResponse.getResultObject().getPhPoint() != 0.0) { Double point = customerPhPointResponse.getResultObject().getPhPoint(); puan.setText(df.format(point)); targetCard.setCardPoint(point); } else { targetCard.setCardPoint(0.0); puan.setText("0,00"); } } else { puan.setText("0,00"); // 可根据需求增加重试逻辑 } }); } // getCardPoint按相同逻辑修改即可 public void getCardPoint(CustomerCardsResultObject targetCard) { currentBindCardId = targetCard.getId(); ongoingApiCall = viewModel.getPoint((getPointResponse, success, errorBody) -> { if (!targetCard.getId().equals(currentBindCardId)) { return; } shimmer.stopShimmer(); shimmer.setVisibility(View.GONE); if (success) { targetCard.setCardPointLoaded(true); DecimalFormat df = new DecimalFormat("0.00"); Double point = getPointResponse.getResultObject(); puan.setText(df.format(point)); targetCard.setCardPoint(point); } else { targetCard.setCardPoint(0.0); puan.setText("0,00"); } }, targetCard.getId()); } }
3. 可选优化
如果列表支持下拉刷新,刷新数据集时可根据需求决定是否重置所有CustomerCardsResultObject的isCardPointLoaded状态为false,重新拉取最新积分;如果滚动时请求并发量过高,可增加全局请求复用逻辑,同一个卡片ID的请求只发起一次,多个监听者共享结果。
内容的提问来源于stack exchange,提问作者emrekocaman
相关产品推荐
相关产品推荐

