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

Room结合PagingSource无法从指定分页位置初始加载数据

问题原因
  • Room自动生成的PagingSource<Int, User>属于位置型PagingSource,其Int类型key的语义是数据在全量查询结果中的位置偏移量,不是页码。你配置pageSize为20时,第6页对应的起始偏移量是5*20=100,传入initialKey=5属于非法偏移值,Paging会做兜底处理,直接回退到偏移量0(第一页)加载。
  • Room 2.5.0以下版本自动生成的PagingSource存在兼容问题,不会正确读取Pager传入的initialKey参数,首次加载始终固定从偏移量0开始取数。
  • 使用位置型PagingSource时如果关闭了enablePlaceholders配置,Paging无法计算位置对应关系,也会忽略传入的initialKey,强制从第一页加载。
实现方案

根据业务需求二选一即可:

方案1:使用Room自动生成的PagingSource

适合仅需要固定初始加载位置、不需要动态跳转到任意分页的场景,需要做3处修改:

  1. 升级Room依赖到2.5.0及以上稳定版本,修复initialKey读取的兼容问题
  2. 修正initialKey取值为对应位置的偏移量,不要传页码
  3. 开启enablePlaceholders = true配置,保证位置计算逻辑正常
    修改后的Pager代码:
const val PAGE_SIZE = 20
// 要加载第6页,对应页码索引为5(从0开始计数)
const val TARGET_INIT_PAGE_INDEX = 5

Pager(
    config = PagingConfig(
        pageSize = PAGE_SIZE,
        prefetchDistance = 5,
        initialLoadSize = PAGE_SIZE,
        enablePlaceholders = true
    ),
    // 偏移量 = 页码索引 * 单页条数
    initialKey = TARGET_INIT_PAGE_INDEX * PAGE_SIZE,
    pagingSourceFactory = { userDao.usersPagingSource() }
).flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)

方案2:自定义PagingSource

适合需要灵活跳转到任意分页、不想依赖Room版本兼容、或者需要优化大表分页性能的场景,可控性更高:

  1. 首先修改UserDao,增加明确的分页查询和总条数查询方法:
@Dao
interface UserDao {
    @Query("SELECT * FROM users ORDER BY id LIMIT :limit OFFSET :offset")
    suspend fun queryUsersByPage(limit: Int, offset: Int): List<User>

    @Query("SELECT COUNT(id) FROM users")
    suspend fun queryUserTotalCount(): Int
}
  1. 自定义PagingSource,可直接用页码作为key,不需要计算偏移量:
class CustomUserPagingSource(
    private val userDao: UserDao,
    private val pageSize: Int,
    private val initPageIndex: Int = 5 // 默认初始加载第6页,可动态传入
) : PagingSource<Int, User>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        return runCatching {
            val currentPage = params.key ?: initPageIndex
            val offset = currentPage * pageSize
            val pageData = userDao.queryUsersByPage(
                limit = params.loadSize,
                offset = offset
            )
            val totalCount = userDao.queryUserTotalCount()

            val prevKey = if (currentPage <= 0) null else currentPage - 1
            val nextKey = if (offset + pageData.size >= totalCount) null else currentPage + 1

            LoadResult.Page(
                data = pageData,
                prevKey = prevKey,
                nextKey = nextKey
            )
        }.getOrElse { LoadResult.Error(it) }
    }

    override fun getRefreshKey(state: PagingState<Int, User>): Int? {
        return state.anchorPosition?.let { anchorPos ->
            state.closestPageToPosition(anchorPos)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPos)?.nextKey?.minus(1)
        }
    }
}
  1. 修改Pager初始化逻辑,不需要再传initialKey:
Pager(
    config = PagingConfig(
        pageSize = 20,
        prefetchDistance = 5,
        initialLoadSize = 20,
        enablePlaceholders = false // 自定义实现不依赖占位符,可按需关闭
    ),
    pagingSourceFactory = { CustomUserPagingSource(userDao, 20) }
).flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)

如果数据量超过10w条,不建议继续用OFFSET分页,可以把PagingSource的key改为上一页最后一条数据的id,用WHERE id > :lastId LIMIT :pageSize的方式做键值分页,性能远高于位置偏移分页。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 18:42:38