Paging 3.0 是 Android Jetpack 组件中用于高效分页加载数据的现代化方案,结合 Kotlin 协程和 Flow 特性,能够显著简化分页逻辑的实现。以下是完整的实现指南和最佳实践:
一、Paging 3.0 核心优势
- 内置加载状态管理:自动跟踪加载状态(加载中/成功/失败)
- 协程与 Flow 原生支持:无缝衔接 Kotlin 异步操作
- 灵活的数据源支持:支持单一数据源(如网络)、混合数据源(网络+数据库)
- 高效的内存管理:自动回收不可见项的内存
- 可组合的架构:轻松添加分隔符、加载动画等
二、快速集成
添加依赖(
build.gradle
):implementation "androidx.paging:paging-runtime-ktx:3.2.1" implementation "androidx.paging:paging-compose:3.2.1" // 如果使用 Jetpack Compose
核心组件:
PagingSource
:定义数据加载逻辑RemoteMediator
:处理多数据源(如网络+数据库)Pager
:配置分页参数并生成数据流PagingDataAdapter
:RecyclerView 的适配器实现
三、基础实现步骤(以网络分页为例)
1. 定义数据源(PagingSource)
class ArticlePagingSource(
private val apiService: ApiService
) : PagingSource<Int, Article>() {
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
return try {
val page = params.key ?: 1
val response = apiService.getArticles(page, params.loadSize)
LoadResult.Page(
data = response.articles,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.isLastPage) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
2. 创建 Repository
class ArticleRepository {
fun getArticleStream() = Pager(
config = PagingConfig(
pageSize = 20,
prefetchDistance = 5,
enablePlaceholders = false
),
pagingSourceFactory = { ArticlePagingSource(apiService) }
).flow
}
3. ViewModel 实现
class ArticleViewModel : ViewModel() {
val articles = ArticleRepository().getArticleStream().cachedIn(viewModelScope)
}
4. UI 层实现(RecyclerView)
class ArticleAdapter : PagingDataAdapter<Article, ArticleViewHolder>(ARTICLE_COMPARATOR) {
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
getItem(position)?.let { article ->
holder.bind(article)
}
}
companion object {
val ARTICLE_COMPARATOR = object : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Article, newItem: Article) =
oldItem == newItem
}
}
}
// Activity/Fragment 中
lifecycleScope.launch {
viewModel.articles.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
四、高级功能实现
1. 混合数据源(网络 + 数据库)
使用 RemoteMediator
:
class ArticleRemoteMediator(
private val db: AppDatabase,
private val api: ApiService
) : RemoteMediator<Int, Article>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Article>
): MediatorResult {
// 根据 loadType 处理不同加载场景
// 1. 从数据库加载缓存
// 2. 请求网络数据
// 3. 更新数据库
// 返回 MediatorResult.Success 或 Error
}
}
2. 加载状态处理
// 在 UI 层添加监听
adapter.addLoadStateListener { loadState ->
when (loadState.refresh) {
is LoadState.Loading -> showLoading()
is LoadState.NotLoading -> hideLoading()
is LoadState.Error -> showError()
}
// 处理分页加载错误
val errorState = loadState.append as? LoadState.Error
?: loadState.prepend as? LoadState.Error
errorState?.let { showRetryButton(it.error) }
}
3. 添加分隔符和加载动画
val pagingData = articlePagingFlow.map { pagingData ->
pagingData.insertSeparators { before, after ->
when {
before?.id?.rem(10) == 0 -> SeparatorItem("Section ${before.id / 10 + 1}")
else -> null
}
}
}
五、性能优化建议
- 合理配置
PagingConfig
:PagingConfig( pageSize = 20, // 每页数量 prefetchDistance = 10, // 预加载距离 enablePlaceholders = true // 是否启用占位符 )
- 使用
cachedIn()
保持数据缓存:.cachedIn(viewModelScope) // 防止配置变更后重新加载
- 网络重试机制:
retry { adapter.retry() // 在错误状态时调用 }
六、常见问题解决
- 页面跳转恢复问题:确保正确实现
getRefreshKey()
- 重复数据问题:检查数据模型的
equals()
和hashCode()
- 内存泄漏:使用
lifecycleScope
管理协程生命周期 - 分页参数不匹配:确认 API 分页策略(页码 vs 游标)
通过 Paging 3.0 的现代化实现方案,开发者可以轻松构建高性能的分页列表,结合 Kotlin 协程和 Flow 的特性,实现更加响应式的 UI 体验。建议根据具体业务需求选择合适的配置策略,并通过 RemoteMediator
实现复杂的多源数据加载场景。