调用PagingAdapter.refresh()导致应用崩溃的问题求助
问题分析与修复方案
1. 修复SwipeRefreshLayout的刷新逻辑
你当前调用feedsAdapter.refresh()后立刻关闭刷新状态,会导致刷新未完成就结束动画,还可能引发状态不一致。正确做法是监听PagingAdapter的加载状态,在刷新完成后再关闭:
binding.swipe.setOnRefreshListener { feedsAdapter?.refresh() } // 监听Adapter加载状态,刷新中保持动画,结束后关闭 feedsAdapter?.addLoadStateListener { loadState -> binding.swipe.isRefreshing = loadState.refresh is LoadState.Loading }
2. 修正PagingSource的分页Key逻辑(核心崩溃原因)
崩溃根源是分页Key计算混淆了页码和偏移量,导致prevKey重复。假设后端接口参数:pageIndex为页码(从0开始),NETWORK_PAGE_SIZE为每页条数,修改如下:
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, FeedsModel> { val pageIndex = params.key ?: STARTING_KEY return try { val response = restApi.getAllFeeds(pageIndex, NETWORK_PAGE_SIZE) val feedsDetails = response.response?.toDomainFeed() ?: emptyList() // 下一页页码:当前页+1,无数据则为null val nextKey = if (feedsDetails.isEmpty()) { null } else { pageIndex + 1 } LoadResult.Page( data = feedsDetails, // 上一页页码:当前页为0则无,否则为当前页-1 prevKey = if (pageIndex == STARTING_KEY) null else pageIndex - 1, nextKey = nextKey ) } catch (exception: IOException) { LoadResult.Error(exception) } catch (exception: HttpException) { LoadResult.Error(exception) } }
同时修正getRefreshKey,确保刷新时能正确获取起始Key:
override fun getRefreshKey(state: PagingState<Int, FeedsModel>): Int? { // 从锚点位置找到最近的Page,返回上一页+1作为刷新起始Key,找不到则返回STARTING_KEY return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) } ?: STARTING_KEY }
3. 修正RemoteMediator的分页与刷新逻辑
3.1 统一分页Key规则
RemoteMediator的分页Key要和PagingSource保持一致(用页码,从0开始),修改load方法中的Key计算:
val page = when(loadType){ LoadType.APPEND -> { val remoteKeys = getRemoteKeyForLastItem(state) val nextKey = remoteKeys?.nextKey ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) nextKey } LoadType.PREPEND -> { val remoteKeys = getRemoteKeyForFirstTime(state) val prevKey = remoteKeys?.prevKey ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) prevKey } LoadType.REFRESH -> { // 下拉刷新直接从第一页加载,清空旧数据 STARTING_KEY } }
3.2 修正RemoteKeys的存储逻辑
确保存入数据库的prevKey和nextKey是正确的页码:
db.withTransaction { if(loadType == LoadType.REFRESH){ db.remoteKeysDao().clearRemoteKeys() db.feedsDaoInterface().clearDB() } // 当前页的上一页页码:当前页为0则无,否则为当前页-1 val prevKey = if(page == STARTING_KEY) null else page - 1 // 当前页的下一页页码:无更多数据则为null,否则为当前页+1 val nextKey = if(paginationEndReached) null else page + 1 val keys = feeds.map { RemoteKeys(feedsId = it.feedId, prevKey = prevKey, nextKey = nextKey) } db.remoteKeysDao().insertAll(keys) db.feedsDaoInterface().insertAllFeeds(*feeds.toDatabasesModel()) }
3.3 实现正确的getRemoteKeyClosestToCurrentPosition
去掉返回null的逻辑,正确实现该方法用于非刷新场景的定位:
private suspend fun getRemoteKeyClosestToCurrentPosition( state: PagingState<Int, FeedsModel> ): RemoteKeys? { return state.anchorPosition?.let { position -> state.closestItemToPosition(position)?.feedId?.let { id -> db.remoteKeysDao().remoteKeysRepoId(id) } } }
4. 确保ViewModel的Pager配置正确
确认ITEMS_PER_PAGE和NETWORK_PAGE_SIZE保持一致,避免分页大小不匹配:
// 确保两个参数值一致 const val NETWORK_PAGE_SIZE = 10 const val ITEMS_PER_PAGE = 10 @OptIn(ExperimentalPagingApi::class) fun getFeeds() = Pager( config = PagingConfig(pageSize = ITEMS_PER_PAGE, enablePlaceholders = false), remoteMediator = FeedsMediator(restApi, myDatabase), pagingSourceFactory = { repository.feedsPagingSource() } ) .flow .cachedIn(viewModelScope)
关键说明
- 崩溃核心原因是之前将
prevKey设置为当前pageIndex,导致连续Page的prevKey重复,Paging3判定为无效分页逻辑抛出异常。 - 下拉刷新时,RemoteMediator的
REFRESH类型会清空数据库并从第一页加载最新数据,确保新Feed出现在列表顶部。 - 所有分页逻辑必须统一使用页码(而非偏移量),PagingSource和RemoteMediator的Key规则要完全一致。
内容的提问来源于stack exchange,提问作者Vivek Gupta
相关产品推荐
相关产品推荐

