Android PagedList离线场景下无法正常展示本地缓存数据的问题
DataSource工厂实现违反Paging 2 规范NetReqDataSourceFactory和DbReqDataSourceFactory都在构造函数里硬编码单个数据源实例,create()方法永远返回同一个对象。Paging 2 要求create()在数据源失效、需要重载时返回全新的DataSource实例,固定返回单实例会导致旧数据源失效后,Paging库拿不到新数据源完成重载,下拉刷新、分类切换的流程直接卡死。- 单例Repository的全局可变成员引发数据串扰
MasterRepository是全局单例,内部的network、reqLiveDataMerger、reqPaged都是共享可变成员。ViewPager中不同status的Fragment依次触发加载时,这些成员会被新的status对应的实例覆盖,导致旧Fragment的数据源、边界回调、网络状态监听全部指向新的分类数据源,出现数据错乱、UI展示其他分类内容的问题。 - 本地数据源加载流程永久挂起
DbReqPageKeyedDataSource.loadInitial()仅在本地查询结果非空时调用callback.onResult(),空结果场景完全不触发回调。Paging库要求无论加载结果是空还是非空,都必须同步触发初始加载回调,否则整个分页流程会永久挂起,不会触发后续的网络请求逻辑。 - 网络数据源失败回调逻辑错误
NetReqPageKeyedDataSource.loadInitial()的失败分支中,虽然回调了空列表,但没有保持正确的分页key,导致Paging库判定列表已经加载到末尾,直接触发零加载回调读取本地缓存,完全跳过后续的网络重试逻辑。 - 下拉刷新逻辑完全失效
下拉刷新回调直接重新调用loadData(),重新创建Adapter、重新observe LiveData,没有触发当前PagedList的失效流程。Paging库本身感知不到刷新操作,不会重新走网络拉取逻辑,下拉刷新没有实际效果。 - BoundaryCallback生命周期不匹配
reqBoundaryCallback是Repository的全局单例成员,内部持有reqLiveDataMerger引用,但这个引用是每次调用getDataReqPage()时新创建的实例,不同分类的边界回调会互相干扰,比如A分类触发零加载时,会把B分类的本地数据塞进A分类的LiveData里。
修正DataSource工厂的实现逻辑
把数据源的初始化挪到create()方法中动态生成,不再硬编码单实例:public class NetReqDataSourceFactory extends DataSource.Factory { private final Context context; private final String status; private final MutableLiveData<NetReqPageKeyedDataSource> networkStatus = new MutableLiveData<>(); public NetReqDataSourceFactory(Context context, String status) { this.context = context; this.status = status; } @Override public DataSource<String, ReqDataList> create() { NetReqPageKeyedDataSource dataSource = new NetReqPageKeyedDataSource(context, status); networkStatus.postValue(dataSource); return dataSource; } // 原有getter方法保持不变 }本地
DbReqDataSourceFactory做同样改造,将DataSource实例的创建从构造函数挪到create()方法内动态生成。拆分Repository的全局可变成员
把全局的network、reqLiveDataMerger、reqPaged全部改为方法内的局部变量,每个status对应的请求生成独立实例,不再被后续请求覆盖。每个请求对应的BoundaryCallback也作为局部变量创建,不再使用全局单例的回调:public LiveData<PagedList<ReqDataList>> getDataReqPage(Context context, String status){ // 每个请求独立生成实例,不污染全局状态 NetReqDataSourceFactory dataSourceFactory = new NetReqDataSourceFactory(context, status); MediatorLiveData<PagedList<ReqDataList>> reqLiveDataMerger = new MediatorLiveData<>(); // 为当前请求创建独立的边界回调,持有当前Merger的引用 PagedList.BoundaryCallback<ReqDataList> boundaryCallback = new PagedList.BoundaryCallback<>() { @Override public void onZeroItemsLoaded() { // 加载当前status对应的本地缓存 LiveData<PagedList<ReqDataList>> localData = getLocalPageData(status); reqLiveDataMerger.addSource(localData, value -> { reqLiveDataMerger.setValue(value); reqLiveDataMerger.removeSource(localData); }); } }; ReqNetwork network = new ReqNetwork(dataSourceFactory, boundaryCallback); reqLiveDataMerger.addSource(network.getPagedMovies(), value -> reqLiveDataMerger.setValue(value)); // 原有数据库写入逻辑保持不变 dataSourceFactory.getData().observeOn(Schedulers.io()).subscribe(item -> { ReqData reqData = new ReqData(); // 原有字段赋值逻辑保持不变 database.dataReqDao().insert(reqData); }); return reqLiveDataMerger; }针对你提到的需要按status传递参数实现本地数据筛选的需求,不需要把
initReqPageDao的逻辑挪到RoomDatabase类中。只要在方法内根据传入的status参数创建对应的DbReqDataSourceFactory,就能实现本地数据的筛选查询,同时避免单例的全局状态污染。修复本地数据源的回调逻辑
无论本地查询结果是否为空,都必须触发初始加载回调,空结果场景传入空列表和合法的初始分页key:@Override public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, ReqDataList> callback) { List<ReqDataList> data; if (filterStatus == null) { data = dataDao.getListAll(); } else { data = dataDao.getListAllByStatus(filterStatus); } // 空结果场景也要回调,避免Paging流程挂起 callback.onResult(data, "0", "1"); }修复网络数据源的失败回调逻辑
初始加载失败时,传入空列表但保持正确的分页key,让Paging库知道后续还可以继续加载,不会判定为列表加载到末尾:@Override public void onFailure(Call<ArrayList<ReqDataList>> call, Throwable t) { String errorMessage = t.getMessage() == null ? "unknown error" : t.getMessage(); networkState.postValue(new NetworkState(NetworkState.Status.FIRST_FAILED, errorMessage)); // 保持分页key为1,不修改分页位置,支持后续重试 callback.onResult(new ArrayList<>(), "1", "2"); }修复下拉刷新逻辑
下拉刷新时不再重新创建Adapter和重新observe,而是保存当前DataSource的引用,调用invalidate()方法让Paging库自动触发全量重载:
在Fragment中维护当前DataSource的引用,下拉时直接让旧数据源失效:private NetReqPageKeyedDataSource currentDataSource; // 网络状态监听中更新当前数据源引用 viewModel.getNetworkStatus().observe(this, netState -> { currentDataSource = dataSourceFactory.getCurrentDataSource(); pageListAdapter.setNetworkState(netState); binding.setState(netState); }); // 下拉刷新回调 binding.swipe.setOnRefreshListener(() -> currentDataSource.invalidate());
内容的提问来源于stack exchange,提问作者Haerul Muttaqin

