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()只重新加载失败的部分,而不是整个数据源。
核心思路:
- 在数据源内部维护一个缓存,保存已成功加载的
Item; - 记录加载失败的位置范围;
- 重试时调用
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
相关产品推荐
相关产品推荐

