Jetpack Paging 终极封装:简洁、通用、高性能的分页加载方案
为了简化 Jetpack Paging 的使用并提升代码的可维护性,可以对其进行封装,提供一个通用的分页加载工具类。以下是优化后的封装方案,支持与 Room 数据库、网络数据源的集成,并提供简洁的 API。
1. 封装思路
通用 PagingSource:封装通用的分页数据加载逻辑,支持 Room 和网络数据源。
PagingHelper:提供分页数据的创建和管理。
Repository 和 ViewModel 集成:通过简洁的 API 实现分页数据的加载和显示。
2. 封装代码
2.1 添加依赖
在 build.gradle 中添加 Paging 和 Room 依赖:
dependencies {
def paging_version = "3.2.1"
implementation "androidx.paging:paging-runtime:$paging_version"
implementation "androidx.paging:paging-compose:$paging_version" // 如果使用 Jetpack Compose
implementation "androidx.room:room-ktx:2.6.1" // Room 支持
}
2.2 定义通用 PagingSource
封装通用的分页数据加载逻辑:
import androidx.paging.PagingSource
import androidx.paging.PagingState
abstract class BasePagingSource<Key : Any, Value : Any> : PagingSource<Key, Value>() {
abstract suspend fun loadData(params: LoadParams<Key>): LoadResult<Key, Value>
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
return try {
loadData(params)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Key, Value>): Key? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey
?: state.closestPageToPosition(anchorPosition)?.nextKey
}
}
}
2.3 定义 PagingHelper
封装分页数据的创建和管理:
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class PagingHelper<Key : Any, Value : Any>(
private val pagingSourceFactory: () -> PagingSource<Key, Value>,
private val pageSize: Int = 20,
private val enablePlaceholders: Boolean = false
) {
fun getPagingData(): Flow<PagingData<Value>> {
return Pager(
config = PagingConfig(
pageSize = pageSize,
enablePlaceholders = enablePlaceholders
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}
2.4 定义 Repository
在 Repository 中使用 PagingHelper 创建分页数据流:
class UserRepository(private val userDao: UserDao) {
fun getUsers(): Flow<PagingData<User>> {
val pagingHelper = PagingHelper<Int, User>(
pagingSourceFactory = { userDao.getUsers() }
)
return pagingHelper.getPagingData()
}
}
2.5 定义 ViewModel
在 ViewModel 中观察分页数据:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val users: Flow<PagingData<User>> = repository.getUsers()
.cachedIn(viewModelScope) // 缓存数据,避免重复加载
}
2.6 在 Activity/Fragment 中绑定数据
使用 PagingDataAdapter 在 RecyclerView 中显示分页数据:
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
adapter = UserAdapter()
recyclerView.adapter = adapter
// 观察分页数据
viewModel.users.observe(this) { pagingData ->
adapter.submitData(lifecycle, pagingData)
}
}
}
class UserAdapter : PagingDataAdapter<User, UserViewHolder>(USER_COMPARATOR) {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
user?.let { holder.bind(it) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder.create(parent)
}
companion object {
private val USER_COMPARATOR = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
}
}
2.7 支持网络数据源
如果需要从网络加载数据,可以自定义 PagingSource:
class UserPagingSource(private val apiService: ApiService) : BasePagingSource<Int, User>() {
override suspend fun loadData(params: LoadParams<Int>): LoadResult<Int, User> {
val page = params.key ?: 1
val response = apiService.getUsers(page, params.loadSize)
return LoadResult.Page(
data = response.users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.users.isEmpty()) null else page + 1
)
}
}
在 Repository 中使用:
class UserRepository(private val apiService: ApiService) {
fun getUsers(): Flow<PagingData<User>> {
val pagingHelper = PagingHelper<Int, User>(
pagingSourceFactory = { UserPagingSource(apiService) }
)
return pagingHelper.getPagingData()
}
}
3. 封装的优势
通用性:通过 BasePagingSource 和 PagingHelper,支持 Room 和网络数据源。
简洁性:提供简洁的 API,减少重复代码。
高性能:支持协程和 Flow,确保线程安全和高效加载。
易扩展:支持自定义 PagingSource 和 RemoteMediator,适用于复杂场景。
4. 进一步优化
4.1 支持依赖注入
结合 Dagger/Hilt,进一步解耦依赖:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideUserRepository(userDao: UserDao): UserRepository {
return UserRepository(userDao)
}
}
4.2 支持边界回调
通过 RemoteMediator 实现网络和本地数据库的混合分页加载。
5. 总结
通过封装 Jetpack Paging,可以显著简化分页加载的实现,提升代码的可维护性和扩展性。关键点包括:
使用 BasePagingSource 封装通用分页逻辑。
使用 PagingHelper 简化分页数据的创建。
结合 Room 和网络数据源,提供灵活的分页加载方案。
这套封装方案适用于大多数 Android 项目,能够显著提升开发效率和代码质量。