Android 之 Jetpack - Paging
以下是一个基于 Android Paging 3 库的完整 Kotlin 实现示例,结合网络分页场景,包含核心组件和最佳实践:
1. 添加依赖(build.gradle)
dependencies {implementation "androidx.paging:paging-runtime-ktx:3.2.1"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"implementation "com.squareup.retrofit2:retrofit:2.9.0" // 网络请求
}
2. 定义数据模型
data class Article(val id: Long,val title: String,val content: String
)
3. 实现 PagingSource(网络数据源)
class ArticlePagingSource(private val apiService: ApiService
) : PagingSource<Int, Article>() {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) page - 1 else null, // 上一页nextKey = if (response.hasMore) page + 1 else null // 下一页)} catch (e: Exception) {LoadResult.Error(e) // 自动传递错误到UI }}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)}}
}
4. 在 ViewModel 中配置数据流
class ArticleViewModel : ViewModel() {private val api = RetrofitClient.create<ApiService>()val articles: Flow<PagingData<Article>> = Pager(config = PagingConfig(pageSize = 20, // 每页数据量prefetchDistance = 5, // 提前加载下一页的阈值(item数量)enablePlaceholders = false // 禁用占位符提升性能 ),pagingSourceFactory = { ArticlePagingSource(api) }).flow.cachedIn(viewModelScope) // 防止重复创建数据流
}
5. 实现 PagingDataAdapter
class ArticleAdapter : PagingDataAdapter<Article, ArticleAdapter.ViewHolder>(DIFF_CALLBACK) {companion object {private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Article>() {override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean =oldItem.id == newItem.id // ID 相同视为同一项[5](@ref)override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean =oldItem == newItem // 内容完全一致}}inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(article: Article) {itemView.findViewById<TextView>(R.id.title).text = article.title}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_article, parent, false)return ViewHolder(view)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {getItem(position)?.let { holder.bind(it) }}
}
6. 在 Activity/Fragment 中集成
class ArticleListFragment : Fragment() {private lateinit var binding: FragmentArticleListBindingprivate val viewModel: ArticleViewModel by viewModels()private val adapter = ArticleAdapter()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {binding.recyclerView.apply {layoutManager = LinearLayoutManager(requireContext())adapter = this@ArticleListFragment.adapter}// 监听加载状态(加载中/错误/完成)adapter.addLoadStateListener { loadState ->when (loadState.refresh) {is LoadState.Loading -> showProgressBar()is LoadState.Error -> showError("加载失败")is LoadState.NotLoading -> hideProgressBar()}}// 绑定数据流lifecycleScope.launch {viewModel.articles.collectLatest { pagingData ->adapter.submitData(pagingData) // 提交分页数据 }}}// 错误重试逻辑private fun showError(message: String) {binding.errorText.text = messagebinding.retryButton.setOnClickListener { adapter.retry() } // 自动重试失败页 }
}
关键配置说明
PagingConfig 参数优化
prefetchDistance
:建议设为屏幕可见项数量的 1-2 倍(如 5-10)maxSize
:设置缓存上限(默认 200),防止内存溢出enablePlaceholders
:关闭占位符可减少内存占用
错误处理机制
adapter.retry()
:自动重试失败的分页请求LoadResult.Error
:在 UI 层通过LoadState
监听错误
性能优化
cachedIn(viewModelScope)
:避免重复创建数据流高效 DiffUtil:通过
areItemsTheSame
快速比较数据变更