You need to enable JavaScript to run this app. Android PagedList离线场景下无法正常展示本地缓存数据的问题
优惠活动
大模型
产品
解决方案
定价
更多

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里。
对应修复方案
  1. 修正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()方法内动态生成。

  2. 拆分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,就能实现本地数据的筛选查询,同时避免单例的全局状态污染。

  3. 修复本地数据源的回调逻辑
    无论本地查询结果是否为空,都必须触发初始加载回调,空结果场景传入空列表和合法的初始分页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");
    }
    
  4. 修复网络数据源的失败回调逻辑
    初始加载失败时,传入空列表但保持正确的分页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");
    }
    
  5. 修复下拉刷新逻辑
    下拉刷新时不再重新创建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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 08:24:06