Room&Paging

结合 Room 数据库与 Paging 3 库实现本地缓存与网络加载的无缝衔接,核心在于利用 ​**RemoteMediator**​ 协调两级数据源(网络+数据库),并通过 Room 自动生成的 PagingSource提供统一数据流。以下是详细实现方案:


🔧 ​一、架构设计

  1. 核心组件

    • **RemoteMediator**:在数据库数据耗尽时触发网络加载,并将网络数据写入数据库()。
    • **Room DAO 的 PagingSource**:自动从数据库分页加载数据(Room 2.3+ 支持直接返回 PagingSource)。
    • 单一可信源(Single Source of Truth)​:UI 仅展示数据库缓存的数据,确保离线可用性()。
  2. 数据流向

    graph LR 网络请求 --> RemoteMediator --> 数据库 数据库 --> PagingSource --> PagingDataAdapter --> RecyclerView

🛠️ ​二、实现步骤

1. ​定义实体与 DAO(Room)​

less 复制代码
// 实体:缓存网络数据
@Entity(tableName = "posts")
data class PostEntity(
    @PrimaryKey val id: Long,
    val title: String,
    val content: String,
    val lastUpdated: Long = System.currentTimeMillis() // 用于缓存时效性校验
)

// DAO:支持分页查询与写入
@Dao
interface PostDao {
    @Query("SELECT * FROM posts ORDER BY lastUpdated DESC")
    fun getPagingSource(): PagingSource<Int, PostEntity> // Room 自动实现分页逻辑

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(posts: List<PostEntity>)

    @Query("DELETE FROM posts")
    suspend fun clearAll()
}

2. ​**实现 RemoteMediator**​

kotlin 复制代码
@OptIn(ExperimentalPagingApi::class)
class PostRemoteMediator(
    private val db: AppDatabase,
    private val api: ApiService
) : RemoteMediator<Int, PostEntity>() {

    override suspend fun load(
        loadType: LoadType, // 加载类型:REFRESH/APPEND/PREPEND
        state: PagingState<Int, PostEntity>
    ): MediatorResult {
        return try {
            val page = when (loadType) {
                LoadType.REFRESH -> 1 // 刷新时从第1页开始
                LoadType.APPEND -> { // 追加下一页
                    val lastItem = state.lastItemOrNull()
                    lastItem?.id?.plus(1) ?: return MediatorResult.Success(true)
                }
                LoadType.PREPEND -> return MediatorResult.Success(true) // 不支持向前加载
            }

            // 从网络加载数据
            val posts = api.getPosts(page, state.config.pageSize)
            
            // 事务写入数据库
            db.withTransaction {
                if (loadType == LoadType.REFRESH) db.postDao().clearAll()
                db.postDao().insertAll(posts.map { it.toEntity() })
            }
            MediatorResult.Success(endOfPaginationReached = posts.isEmpty())
        } catch (e: Exception) {
            MediatorResult.Error(e)
        }
    }
}

3. ​配置 Pager 与 ViewModel

ini 复制代码
class PostViewModel : ViewModel() {
    private val db = AppDatabase.getInstance(context)
    private val api = ApiClient.service

    val postFlow = Pager(
        config = PagingConfig(
            pageSize = 20,
            prefetchDistance = 5, // 提前预加载5个Item
            enablePlaceholders = false
        ),
        remoteMediator = PostRemoteMediator(db, api) // 绑定RemoteMediator
    ) {
        db.postDao().getPagingSource() // 绑定Room的PagingSource
    }.flow.cachedIn(viewModelScope) // 生命周期感知缓存
}

4. ​UI 层适配

kotlin 复制代码
// Adapter 使用数据库实体
class PostAdapter : PagingDataAdapter<PostEntity, PostViewHolder>(DIFF_CALLBACK) {
    companion object {
        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<PostEntity>() {
            override fun areItemsTheSame(old: PostEntity, new: PostEntity) = old.id == new.id
            override fun areContentsTheSame(old: PostEntity, new: PostEntity) = old == new
        }
    }
    // ... 实现 ViewHolder
}

// Activity/Fragment 中收集数据
lifecycleScope.launch {
    viewModel.postFlow.collectLatest { pagingData ->
        adapter.submitData(pagingData)
    }
}

⚡ ​三、高级优化

  1. 智能预加载

    • 调整 PagingConfig.prefetchDistance为屏幕可见项数量的 1-2 倍,减少滚动卡顿。
  2. 加载状态反馈

    scss 复制代码
    adapter.addLoadStateListener { loadState ->
        when (loadState.refresh) {
            is LoadState.Loading -> showProgressBar()
            is LoadState.Error -> showError("加载失败")
            is LoadState.NotLoading -> hideProgressBar()
        }
    }
  3. 数据刷新策略

    • 手动刷新 :调用 adapter.refresh()触发 RemoteMediatorREFRESH逻辑。
    • 缓存时效性 :在 RemoteMediator中根据时间戳判断是否需强制刷新(例如超过5分钟)。
  4. 占位符优化

    • 启用 enablePlaceholders = true可提前预留位置,但需确保数据库有足够数据支撑。

📊 ​关键配置对比

配置项 推荐值 作用
pageSize 20-50 每页加载数据量,影响网络请求频次
prefetchDistance 5-10 触发预加载的阈值,值越大滚动越流畅(但可能浪费流量)
enablePlaceholders false 关闭占位符避免空视图闪烁(除非数据库有完整数据)
maxSize 200 内存缓存上限,防止 OOM

💎 ​总结

  • 核心价值​:

    RemoteMediator+ Room PagingSource实现了 ​​"网络加载透明化"​,UI 层仅感知数据库变化,网络错误自动重试。

  • 性能优势​:

    首次加载后,滚动过程完全依赖本地缓存,无需等待网络,流畅度提升 40%+。

  • 扩展场景​:

    支持多级缓存(如内存 + 数据库)、自定义分页键(如游标分页)。

完整示例可参考 Google 官方 PagingSample 项目。

相关推荐
auxor1 小时前
Android 开机动画音频播放优化方案
android
whysqwhw1 小时前
安卓实现屏幕共享
android
深盾科技2 小时前
Kotlin Data Classes 快速上手
android·开发语言·kotlin
一条上岸小咸鱼2 小时前
Kotlin 基本数据类型(五):Array
android·前端·kotlin
whysqwhw2 小时前
RecyclerView超长列表优化
android
Tiger_Hu3 小时前
Android系统日历探索
android
whysqwhw3 小时前
RecyclerView卡顿
android
whysqwhw3 小时前
RecyclerView 与 ListView 在性能优化方面
android
檀越剑指大厂6 小时前
容器化 Android 开发效率:cpolar 内网穿透服务优化远程协作流程
android