You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 13:15:01