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 项目。

相关推荐
林北北的霸霸9 小时前
django初识与安装
android·mysql·adb
Java 码农11 小时前
MySQL EXPLAIN 详解与优化指南
android·mysql·adb
stevenzqzq15 小时前
Android Hilt 入门教程_传统写法和Hilt写法的比较
android
wuwu_q15 小时前
用通俗易懂方式,详细讲讲 Kotlin Flow 中的 map 操作符
android·开发语言·kotlin
_李小白16 小时前
【Android FrameWork】第五天:init加载RC文件
android
2501_9160074716 小时前
手机使用过的痕迹能查到吗?完整查询指南与步骤
android·ios·智能手机·小程序·uni-app·iphone·webview
黄毛火烧雪下17 小时前
React Native (RN)项目在web、Android和IOS上运行
android·前端·react native
下位子17 小时前
『OpenGL学习滤镜相机』- Day7: FBO(帧缓冲对象)
android·opengl
從南走到北17 小时前
JAVA国际版同城外卖跑腿团购到店跑腿多合一APP系统源码支持Android+IOS+H5
android·java·ios·微信小程序·小程序
空白格9717 小时前
组件化攻略
android