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

Android Paging Library如何为loadRange/loadAfter实现重试功能?

解决Android Paging Library局部加载重试的问题

嘿,我完全理解你不想用dataSource.invalidate()的顾虑——重置整个数据源确实会浪费已加载的资源,太不高效了。针对你的需求,这里有几个更精准的方案,不用丢弃整个数据源就能实现loadRange()的重试:

方案一:升级到Paging 3(推荐)

如果你还在使用Paging 2,强烈建议升级到Paging 3,它原生支持局部加载重试,不需要自己维护复杂的状态逻辑。

Paging 3的PagingSource在加载失败时会返回LoadResult.Error,你可以直接通过PagingDataAdapter.retry()触发最近一次失败请求的重试,整个过程不会丢弃已加载的数据。

示例代码:

public class MyPagingSource extends PagingSource<Integer, Item> {

    @Override
    public LoadResult<Integer, Item> load(@NonNull LoadParams<Integer> params) {
        try {
            // 计算当前页码(如果是基于位置的加载,可转换成对应的位置范围)
            int currentPage = params.getKey() != null ? params.getKey() : 1;
            ArrayList<Item> items = executeCall(currentPage, params.loadSize);

            // 计算下一页和上一页的key
            Integer prevPage = currentPage > 1 ? currentPage - 1 : null;
            Integer nextPage = items.size() == params.loadSize ? currentPage + 1 : null;

            return LoadResult.Page(items, prevPage, nextPage);
        } catch (IOException e) {
            // 加载失败时返回错误结果,Paging会保存这个错误状态
            return LoadResult.Error(e);
        }
    }

    @Override
    public Integer getRefreshKey(@NonNull PagingState<Integer, Item> state) {
        // 生成刷新时的起始key,这里以锚点位置对应的页码为例
        Integer anchorPosition = state.getAnchorPosition();
        if (anchorPosition == null) return 1;
        PagingSource.LoadResult.Page<Integer, Item> anchorPage = state.closestPageToPosition(anchorPosition);
        return anchorPage != null ? anchorPage.getPrevKey() + 1 : 1;
    }
}

在UI层,给重试按钮绑定点击事件即可:

retryButton.setOnClickListener(v -> pagingDataAdapter.retry());

Paging会自动重试最近一次失败的加载请求,不会影响已加载的数据。

方案二:Paging 2下的局部重试(结合缓存+轻量invalidate)

如果暂时无法升级到Paging 3,你可以通过维护本地缓存+记录失败范围的方式,让invalidate()只重新加载失败的部分,而不是整个数据源。

核心思路:

  1. 在数据源内部维护一个缓存,保存已成功加载的Item;
  2. 记录加载失败的位置范围;
  3. 重试时调用invalidate(),新的数据源实例会先从缓存读取数据,只重新加载之前失败的范围。

示例代码:

public class MyDataSource extends PositionalDataSource<Item> {
    // 缓存已加载的Item,用位置作为key
    private final SparseArray<Item> itemCache = new SparseArray<>();
    // 记录加载失败的位置范围
    private final Set<LoadRange> failedRanges = new HashSet<>();
    private int totalCount;

    // 辅助类:封装失败的加载范围
    private static class LoadRange {
        int start;
        int end;

        LoadRange(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            LoadRange range = (LoadRange) o;
            return start == range.start && end == range.end;
        }

        @Override
        public int hashCode() {
            return Objects.hash(start, end);
        }
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Item> callback) {
        ArrayList<Item> items = new ArrayList<>();
        // 先从缓存读取已有的数据
        for (int i = 0; i < params.requestedLoadSize; i++) {
            Item item = itemCache.get(i);
            if (item != null) items.add(item);
        }

        // 计算需要从网络加载的范围
        int missingStart = items.size();
        int missingEnd = params.requestedLoadSize - 1;
        if (missingStart <= missingEnd) {
            ArrayList<Item> networkItems = executeCall(missingStart, missingEnd - missingStart + 1);
            if (networkItems != null) {
                items.addAll(networkItems);
                // 更新缓存
                for (int i = 0; i < networkItems.size(); i++) {
                    itemCache.put(missingStart + i, networkItems.get(i));
                }
                // 移除已成功加载的失败范围
                failedRanges.remove(new LoadRange(missingStart, missingEnd));
            } else {
                // 记录失败范围
                failedRanges.add(new LoadRange(missingStart, missingEnd));
            }
        }

        callback.onResult(items, 0, totalCount);
    }

    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Item> callback) {
        ArrayList<Item> items = new ArrayList<>();
        // 先从缓存读取已有的数据
        for (int i = params.startPosition; i < params.startPosition + params.loadSize; i++) {
            Item item = itemCache.get(i);
            if (item != null) items.add(item);
        }

        // 计算需要从网络加载的范围
        int missingStart = params.startPosition + items.size();
        int missingEnd = params.startPosition + params.loadSize - 1;
        if (missingStart <= missingEnd) {
            ArrayList<Item> networkItems = executeCall(missingStart, missingEnd - missingStart + 1);
            if (networkItems != null) {
                items.addAll(networkItems);
                // 更新缓存
                for (int i = 0; i < networkItems.size(); i++) {
                    itemCache.put(missingStart + i, networkItems.get(i));
                }
                failedRanges.remove(new LoadRange(missingStart, missingEnd));
            } else {
                failedRanges.add(new LoadRange(missingStart, missingEnd));
            }
        }

        callback.onResult(items);
    }

    // 对外提供重试方法
    public void retryFailedLoads() {
        if (!failedRanges.isEmpty()) {
            // 触发数据源刷新,但因为有缓存,只会重新加载失败的范围
            invalidate();
        }
    }

    // 你的网络请求方法
    private ArrayList<Item> executeCall(int startPosition, int loadSize) {
        // ... 原有的网络请求逻辑
    }
}

这样当你调用retryFailedLoads()时,虽然调用了invalidate(),但新的数据源会优先使用缓存中的数据,只重新加载之前失败的位置范围,避免了资源浪费。


内容的提问来源于stack exchange,提问作者Naveen Dissanayake

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:53:50