目录
  1. 1. 一、核心组件
  2. 2. 二、RemoteMediator:网络 + 本地缓存
  3. 3. 三、UI 层集成
  4. 4. 面试常考问题
JetPack全家桶(六)之Paging分页库

Paging 是 Jetpack 中专门解决列表分页加载的库,它按需加载数据,避免一次性查询大量数据导致的内存压力与 UI 卡顿。Paging 3 以 Kotlin Coroutines + Flow 为核心,提供了简洁的响应式分页 API。

一、核心组件

PagingSource:定义数据源以及如何从其中加载数据。每个 PagingSource 需实现 load() 方法,接收 LoadParams,返回 LoadResult

class ArticlePagingSource(
private val api: ApiService
) : PagingSource<Int, Article>() {

override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchor ->
state.closestPageToPosition(anchor)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchor)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
return try {
val page = params.key ?: 1
val response = api.getArticles(page, params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page > 1) page - 1 else null,
nextKey = if (response.hasNext) page + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}

PagingData:分页数据的容器,通过 Pager 创建,内部以 Flow 形式暴露给 UI 层。

PagingDataAdapter:继承自 RecyclerView.Adapter,配合 DiffUtil.ItemCallback 高效对比数据差异,驱动列表增量更新。

二、RemoteMediator:网络 + 本地缓存

当需要”先展示本地缓存,再加载网络数据并回写本地”的双重数据源模式时,使用 RemoteMediator:

@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
private val db: AppDatabase,
private val api: ApiService
) : RemoteMediator<Int, Article>() {

override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Article>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> 1
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull() ?: return MediatorResult.Success(true)
lastItem.page + 1
}
}
return try {
val response = api.getArticles(page, state.config.pageSize)
db.withTransaction {
if (loadType == LoadType.REFRESH) {
db.articleDao().clearAll()
}
db.articleDao().insertAll(response.items)
}
MediatorResult.Success(endOfPaginationReached = !response.hasNext)
} catch (e: Exception) {
MediatorResult.Error(e)
}
}
}

Paging 与 Room 自动集成 —— 只需返回 PagingSource 或使用 @Query 直接返回 PagingSource,Room 会在数据变更时自动触发页面刷新。

三、UI 层集成

@AndroidEntryPoint
class ArticleListFragment : Fragment() {
private val viewModel: ArticleViewModel by viewModels()
private val adapter = ArticlePagingAdapter()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
viewModel.pagingDataFlow.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
}
}

面试常考问题

Q1:PagingSource 的 getRefreshKey() 作用?

当 PagingData 失效(如数据库更新)需要刷新时,getRefreshKey() 返回用于重新加载的 key,确保刷新后列表位置不错乱。通常取当前可见 item 所在页的前一页或后一页 key。

Q2:Paging 2 vs Paging 3 主要区别?

Paging 3 全面拥抱 Kotlin Coroutines/Flow,去除了 PagedList/DataSource 等旧 API。加载状态通过 loadStateFlow 统一暴露,支持 retry 操作。Paging 2 基于 RxJava/LiveData,API 较为分散。

Q3:RemoteMediator 中三种 LoadType 分别何时触发?

REFRESH 在 PagingData 初始化或手动调用 refresh() 时触发;PREPEND 在列表顶部上拉加载更早数据时触发;APPEND 在列表底部滚动触发加载更多时触发。源码定义在 androidx.paging.LoadType

打赏
  • 微信
  • 支付宝

评论