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

相关推荐
消失的旧时光-1943几秒前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon1 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon1 小时前
VSYNC 信号完整流程2
android
dalancon1 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013842 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android3 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才3 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶4 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙4 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github
qq_283720055 小时前
MySQL技巧(四): EXPLAIN 关键参数详细解释
android·adb