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

相关推荐
踏雪羽翼8 小时前
android TextView实现文字字符不同方向显示
android·自定义view·textview方向·文字方向·textview文字显示方向·文字旋转·textview文字旋转
lxysbly8 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
夏沫琅琊11 小时前
Android 各类日志全面解析(含特点、分析方法、实战案例)
android
程序员JerrySUN11 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
TeleostNaCl12 小时前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte113 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn14 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪15 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥15 小时前
Android分层
android
极客小云17 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试