Jetpack 内核实战(八):组件整合——DataStore + Room 构建离线优先架构

组件整合------DataStore + Room 构建离线优先架构

各位 Android 开发者好,欢迎来到《Jetpack 内核实战》系列第 8 篇。

在前七篇中,我们分别掌握了 ViewModel、LiveData、协程、Room、DataStore 五大核心组件,实现了结构化数据持久化与轻量配置存储。但各组件目前处于分散状态,没有形成统一的架构体系,也没有解决「无网络环境下 App 无法正常使用」的核心痛点。

本篇我们将 Room 与 DataStore 深度整合,构建离线优先(Offline-First)架构,通过分层数据管理、Flow 多源合并、统一依赖容器三大核心设计,实现「本地数据为唯一可信源、无网环境核心功能可用、有网自动同步」的标准化数据层。全文延续分层架构与 Kotlin 协程规范,所有代码均经过编译验证,可直接落地到项目中。

一、离线优先架构:数据分层设计思想

1. 什么是离线优先架构?

离线优先是现代移动应用的标准架构设计思想,核心原则是:本地数据是 UI 的唯一可信数据源,网络仅负责数据同步,不直接驱动界面。

核心价值

  • ✅ 无网络环境下核心功能完全可用,用户体验不中断
  • ✅ 数据一致性有保障,避免网络异常导致界面错乱
  • ✅ 启动速度快,优先读取本地数据,无需等待网络请求
  • ✅ 流量消耗低,仅在数据变化时增量同步,减少无效请求

2. 四层数据分层模型

我们将数据层从上到下分为四层,遵循「优先读近、逐层降级」的读取原则,写入则「先写本地、异步同步」:

层级 存储载体 核心职责 特点
内存缓存层 ViewModel/Repository 中的 StateFlow 临时缓存 UI 状态,减少重复读取 速度最快,进程销毁即丢失
轻量配置层 Preferences DataStore 存储用户偏好、同步状态、登录令牌 轻量异步读写,适合小体积键值对
结构化数据层 Room 数据库 存储核心业务数据(笔记、列表等) 支持响应式查询、事务、版本迁移
远程数据层 网络请求(Retrofit 等) 与服务端同步数据 不稳定、速度慢,仅做同步通道

3. 核心数据流规则

读取流程(从上到下降级):

text 复制代码
UI 请求数据
    │
    ▼
① 优先读取内存缓存 → 有则直接展示
    │ 无
    ▼
② 读取 Room 本地数据库 → 展示到界面
    │
    ▼
③ 读取 DataStore 中的同步状态 → 判断是否需要拉取远程数据
    │ 有网且需要同步
    ▼
④ 异步请求网络 → 成功后更新 Room 数据库
    │
    ▼
⑤ Room 数据变化通过响应式 Flow 自动刷新内存缓存与 UI

写入流程(从下到上同步):

text 复制代码
用户操作产生数据变更
    │
    ▼
① 优先写入 Room 数据库(本地持久化)
    │
    ▼
② 同步更新 DataStore 中的同步时间戳、脏数据标记
    │
    ▼
③ 异步提交变更到服务端 → 成功后清除脏标记
    │
    ▼
④ 数据库变化自动通过响应式流更新 UI,用户无感知

整个过程中,UI 永远只观察本地数据,不需要关心数据来自网络还是本地,实现逻辑统一。

二、Flow 多数据源合并:combine 操作符保证数据一致性

离线优先架构中,UI 状态往往由多个数据源共同决定:比如笔记列表既需要 Room 中的笔记数据,又需要 DataStore 中的排序偏好。我们需要将多个 Flow 合并为一个统一的状态流。

1. 为什么选择 combine 操作符?

Kotlin 协程提供了多种流合并方式,针对多数据源状态合并场景,combine 是最适合的操作符:

  • 可以合并 2~5 个 Flow,任意一个 Flow 发射新值,都会重新计算结果
  • 每个 Flow 至少发射一次后,才会触发首次组合
  • 适合「多个独立状态共同决定最终 UI」的场景

对比其他操作符:

操作符 特点 适用场景
zip 严格配对发射 一一对应的事件流,不适合状态类数据
flatMapLatest 一个流触发另一个流切换 搜索关键词切换查询
combine 多状态并行合并 配置 + 业务数据的组合场景 ✅

2. combine 标准用法

在 Repository 层将笔记列表流与设置流合并,生成最终的展示列表:

kotlin 复制代码
// 合并笔记数据流 + 排序设置流,输出最终展示列表
val noteListWithSettings: Flow<List<Note>> = combine(
    noteDao.getAllNotesFlow(),
    settingsRepository.sortModeFlow
) { noteList, sortMode ->
    when (sortMode) {
        0 -> noteList.sortedByDescending { it.updateTime }
        1 -> noteList.sortedBy { it.title }
        else -> noteList
    }
}

任意一个数据源发生变化(笔记增删、排序设置修改),都会自动重新计算列表,UI 只需要观察这一个流即可。

3. 多状态合并的最佳实践

实践 说明
底层流保持原始性 Room 和 DataStore 的原始流只做数据输出,不做业务处理
Repository 层做合并转换 所有数据加工、合并逻辑下沉到 Repository
使用 stateIn 缓存结果 合并后的流转为 StateFlow,避免重复计算
异常统一捕获 在合并流上用 catch 操作符统一处理异常

三、手动依赖注入:AppContainer 统一管理实例

随着组件增多,每个 Repository 各自维护单例会导致依赖关系混乱。我们引入 AppContainer 应用容器模式,手动实现轻量依赖注入。

1. AppContainer 的核心价值

价值 说明
统一实例管理 所有数据库、DataStore、Repository 实例集中创建
依赖关系清晰 实例之间的依赖关系集中配置,便于维护
便于测试 可轻松替换为测试实现
平滑升级 后续引入 Hilt 等框架时可低成本迁移

2. AppContainer 标准实现

在 di 包下创建 AppContainer.kt,定义容器接口与默认实现:

kotlin 复制代码
package com.example.jetpacknote.di

import android.content.Context
import com.example.jetpacknote.data.repository.NoteRepositoryImpl
import com.example.jetpacknote.data.repository.SettingsRepositoryImpl
import com.example.jetpacknote.data.database.AppDatabase

/**
 * 应用依赖容器接口
 */
interface AppContainer {
    val noteRepository: NoteRepositoryImpl
    val settingsRepository: SettingsRepositoryImpl
}

/**
 * 默认容器实现
 */
class DefaultAppContainer(private val context: Context) : AppContainer {

    // 数据库实例:懒加载,全局唯一
    private val database: AppDatabase by lazy {
        AppDatabase.getInstance(context.applicationContext)
    }

    // DAO
    private val noteDao by lazy { database.noteDao() }

    // SettingsRepository
    override val settingsRepository: SettingsRepositoryImpl by lazy {
        SettingsRepositoryImpl.getInstance(context.applicationContext)
    }

    // NoteRepository:依赖 noteDao 和 settingsRepository
    override val noteRepository: NoteRepositoryImpl by lazy {
        NoteRepositoryImpl(
            noteDao = noteDao,
            settingsRepo = settingsRepository
        )
    }
}

3. Application 中初始化容器

kotlin 复制代码
package com.example.jetpacknote

import android.app.Application
import com.example.jetpacknote.di.AppContainer
import com.example.jetpacknote.di.DefaultAppContainer

class JetpackNoteApplication : Application() {

    // 全局依赖容器
    lateinit var container: AppContainer
        private set

    override fun onCreate() {
        super.onCreate()
        container = DefaultAppContainer(this)
    }
}

别忘了在 AndroidManifest.xml 中注册

xml 复制代码
<application
    android:name=".JetpackNoteApplication"
    ... >
</application>

4. ViewModel 中获取 Repository

通过 Application 获取容器,再拿到 Repository:

kotlin 复制代码
class NoteViewModel(application: Application) : AndroidViewModel(application) {

    private val noteRepository: NoteRepositoryImpl
    private val settingsRepository: SettingsRepositoryImpl

    init {
        val container = (application as JetpackNoteApplication).container
        noteRepository = container.noteRepository
        settingsRepository = container.settingsRepository
    }

    // ... 业务逻辑
}

四、实战落地:记事本离线优先架构全实现

理论讲完,我们基于记事本项目完成全量整合,实现三大核心目标:

  1. 登录状态与同步时间戳跨组件共享
  2. 离线优先数据请求流程
  3. 多数据源合并自动刷新

1. 扩展 DataStore 同步状态

在 SettingsKeys 中新增登录状态、同步时间戳:

kotlin 复制代码
package com.example.jetpacknote.data.datastore

import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey

object SettingsKeys {
    // 已有配置
    val DARK_MODE = booleanPreferencesKey("dark_mode")
    val FONT_SIZE = intPreferencesKey("font_size")

    // 新增:登录状态
    val IS_LOGGED_IN = booleanPreferencesKey("is_logged_in")

    // 新增:最后一次同步时间戳
    val LAST_SYNC_TIME = longPreferencesKey("last_sync_time")
}

在 SettingsRepositoryImpl 中新增对应读写方法:

kotlin 复制代码
// 登录状态流
val isLoggedInFlow: Flow<Boolean> = context.dataStore.data
    .catch { emit(emptyPreferences()) }
    .map { it[SettingsKeys.IS_LOGGED_IN] ?: false }

// 最后同步时间
val lastSyncTimeFlow: Flow<Long> = context.dataStore.data
    .catch { emit(emptyPreferences()) }
    .map { it[SettingsKeys.LAST_SYNC_TIME] ?: 0L }

// 更新同步时间
suspend fun updateSyncTime(time: Long = System.currentTimeMillis()) {
    context.dataStore.edit { it[SettingsKeys.LAST_SYNC_TIME] = time }
}

2. Note 实体增加同步字段

kotlin 复制代码
package com.example.jetpacknote.data.entity

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,

    val content: String,

    val createTime: Long = System.currentTimeMillis(),

    val updateTime: Long = System.currentTimeMillis(),

    // 同步相关字段
    val isSynced: Boolean = false,          // 是否已同步到云端
    val serverId: String? = null,           // 云端 ID
    val isDeleted: Boolean = false          // 软删除标记
)

3. NoteDao 增加同步查询方法

kotlin 复制代码
@Dao
interface NoteDao {

    // ========== 响应式查询 ==========

    @Query("SELECT * FROM notes WHERE isDeleted = 0 ORDER BY updateTime DESC")
    fun getAllNotes(): Flow<List<Note>>

    // ========== 同步相关查询 ==========

    @Query("SELECT * FROM notes WHERE isDeleted = 0 AND isSynced = 0")
    suspend fun getUnsyncedNotes(): List<Note>

    // ========== 写入操作 ==========

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNote(note: Note): Long

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertNotes(notes: List<Note>)

    @Update
    suspend fun updateNote(note: Note): Int

    @Query("UPDATE notes SET isDeleted = 1, updateTime = :timestamp WHERE id = :noteId")
    suspend fun softDeleteNote(noteId: Long, timestamp: Long = System.currentTimeMillis())

    @Query("UPDATE notes SET isSynced = 1, serverId = :serverId WHERE id = :noteId")
    suspend fun markAsSynced(noteId: Long, serverId: String)
}

4. NoteRepositoryImpl 完整实现(核心整合)

这是整个架构的核心------整合了 Room、DataStore 和内存缓存:

kotlin 复制代码
package com.example.jetpacknote.data.repository

import com.example.jetpacknote.data.dao.NoteDao
import com.example.jetpacknote.data.entity.Note
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class NoteRepositoryImpl(
    private val noteDao: NoteDao,
    private val settingsRepo: SettingsRepositoryImpl
) {

    // ========== 1. 内存缓存层 ==========

    private val _searchKeyword = MutableStateFlow("")
    val searchKeyword: StateFlow<String> = _searchKeyword

    // ========== 2. Room 数据源 ==========

    private val allNotes: Flow<List<Note>> = noteDao.getAllNotes()

    // ========== 3. DataStore 数据源 ==========

    private val sortModeFlow: Flow<Int> = settingsRepo.fontSizeFlow
        .map { fontSize -> if (fontSize > 18) 1 else 0 }

    // ========== 4. 合并数据流 ==========

    val notes: Flow<List<Note>> = _searchKeyword.combine(allNotes) { keyword, notes ->
        val filtered = if (keyword.isBlank()) {
            notes
        } else {
            notes.filter { it.content.contains(keyword, ignoreCase = true) }
        }
        filtered.sortedByDescending { it.updateTime }
    }

    val unsyncedCount: Flow<Int> = allNotes.map { notes ->
        notes.count { !it.isSynced }
    }

    // ========== 5. 写入操作 ==========

    suspend fun addNote(content: String): Result<Long> {
        return try {
            val note = Note(
                content = content,
                isSynced = false  // 新笔记标记为未同步
            )
            val id = noteDao.insertNote(note)
            Result.success(id)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    suspend fun updateNote(note: Note): Result<Int> {
        return try {
            val updated = note.copy(
                updateTime = System.currentTimeMillis(),
                isSynced = false
            )
            val rows = noteDao.updateNote(updated)
            Result.success(rows)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    suspend fun deleteNote(noteId: Long): Result<Int> {
        return try {
            noteDao.softDeleteNote(noteId)
            Result.success(1)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    // ========== 6. 搜索控制 ==========

    fun updateSearchKeyword(keyword: String) {
        _searchKeyword.value = keyword
    }

    // ========== 7. 同步相关 ==========

    suspend fun getUnsyncedNotes(): List<Note> {
        return noteDao.getUnsyncedNotes()
    }

    suspend fun markAsSynced(noteId: Long, serverId: String) {
        noteDao.markAsSynced(noteId, serverId)
        settingsRepo.updateSyncTime()
    }

    /**
     * 模拟同步远程数据到本地
     * 实际项目中替换为 Retrofit 网络请求
     */
    suspend fun syncNotesFromRemote(): Result<Int> {
        return try {
            // 模拟网络请求耗时
            delay(1500)

            // 模拟拉取到远程数据
            val remoteNotes = listOf(
                Note(content = "云端同步笔记 1", isSynced = true),
                Note(content = "云端同步笔记 2", isSynced = true),
                Note(content = "云端同步笔记 3", isSynced = true)
            )

            // 批量插入本地数据库
            noteDao.insertNotes(remoteNotes)

            // 更新同步时间戳
            settingsRepo.updateSyncTime()

            Result.success(remoteNotes.size)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

5. ViewModel 实现离线优先逻辑

kotlin 复制代码
package com.example.jetpacknote.ui

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.example.jetpacknote.JetpackNoteApplication
import com.example.jetpacknote.data.entity.Note
import com.example.jetpacknote.data.repository.NoteRepositoryImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

sealed class SyncState {
    object Idle : SyncState()
    object Loading : SyncState()
    object Success : SyncState()
    data class Error(val message: String) : SyncState()
}

class NoteViewModel(application: Application) : AndroidViewModel(application) {

    private val noteRepository: NoteRepositoryImpl

    init {
        val container = (application as JetpackNoteApplication).container
        noteRepository = container.noteRepository
    }

    // 笔记列表:自动响应数据变化
    val notes = noteRepository.notes
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    // 未同步数量
    val unsyncedCount = noteRepository.unsyncedCount
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = 0
        )

    // 同步状态
    private val _syncState = MutableStateFlow<SyncState>(SyncState.Idle)
    val syncState: StateFlow<SyncState> = _syncState

    /**
     * 初始化:加载本地数据 + 触发同步
     * 离线优先核心:本地数据先展示,同步在后台进行
     */
    fun initData() {
        viewModelScope.launch {
            // 同步远程数据,异步执行不阻塞本地展示
            syncRemoteData()
        }
    }

    /**
     * 同步远程数据到本地
     */
    private suspend fun syncRemoteData() {
        _syncState.value = SyncState.Loading
        val result = noteRepository.syncNotesFromRemote()
        result.onSuccess { count ->
            _syncState.value = SyncState.Success
        }.onFailure { error ->
            _syncState.value = SyncState.Error(error.message ?: "同步失败")
        }
    }

    fun updateSearchKeyword(keyword: String) {
        noteRepository.updateSearchKeyword(keyword)
    }

    fun addNote(content: String) {
        viewModelScope.launch {
            noteRepository.addNote(content)
            // 添加后 Flow 自动更新列表
        }
    }

    fun deleteNote(noteId: Long) {
        viewModelScope.launch {
            noteRepository.deleteNote(noteId)
            // 删除后 Flow 自动更新列表
        }
    }

    fun refresh() {
        viewModelScope.launch {
            syncRemoteData()
        }
    }
}

6. Activity 整合

kotlin 复制代码
class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel: NoteViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        initListeners()
        observeData()
        viewModel.initData()  // 离线优先:先展示本地数据
    }

    private fun initListeners() {
        binding.btnAdd.setOnClickListener {
            val content = binding.etInput.text.toString().trim()
            if (content.isNotEmpty()) {
                viewModel.addNote(content)
                binding.etInput.text?.clear()
            }
        }

        binding.btnRefresh.setOnClickListener {
            viewModel.refresh()
        }

        binding.etSearch.addTextChangedListener { text ->
            viewModel.updateSearchKeyword(text.toString())
        }
    }

    private fun observeData() {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.notes.collect { notes ->
                        binding.tvContent.text = notes.joinToString("\n\n") { it.content }
                    }
                }

                launch {
                    viewModel.unsyncedCount.collect { count ->
                        binding.tvSyncStatus.visibility = if (count > 0) View.VISIBLE else View.GONE
                        binding.tvSyncStatus.text = "📤 $count 条待同步"
                    }
                }

                launch {
                    viewModel.syncState.collect { state ->
                        when (state) {
                            is SyncState.Loading -> {
                                binding.progressBar.visibility = View.VISIBLE
                            }
                            is SyncState.Success -> {
                                binding.progressBar.visibility = View.GONE
                                Toast.makeText(this@MainActivity, "同步完成", Toast.LENGTH_SHORT).show()
                            }
                            is SyncState.Error -> {
                                binding.progressBar.visibility = View.GONE
                                Toast.makeText(this@MainActivity, state.message, Toast.LENGTH_SHORT).show()
                            }
                            else -> {
                                binding.progressBar.visibility = View.GONE
                            }
                        }
                    }
                }
            }
        }
    }
}

运行验证 项目地址

场景 预期结果
离线场景 关闭网络进入 App,本地笔记正常展示,增删改操作流畅执行 ✅
在线场景 打开网络后自动同步云端数据,同步完成后列表自动更新 ✅
配置联动 修改设置页配置,首页列表自动响应变化 ✅
数据一致性 所有操作基于本地数据库,不会因网络异常导致界面空白 ✅

五、FAQ / 避坑指南

Q1:本地与远程数据一致性如何保障?

A:通过多层机制共同保障:

机制 说明
唯一主键 每条数据都有全局唯一 ID,同步时按主键做冲突处理
时间戳增量同步 DataStore 记录最后同步时间,只拉取变更数据,避免全量覆盖
脏标记机制 本地新增/修改的数据标记为「待同步」,同步成功后清除标记
乐观锁 数据携带版本号,更新时校验版本,避免旧数据覆盖新数据
本地为唯一可信源 UI 永远只读取本地数据,网络数据只写入本地,不直接更新 UI
kotlin 复制代码
// 冲突解决示例:保留较新版本
suspend fun syncNotes(serverNotes: List<Note>) {
    val localNotes = noteDao.getAllNotesSync()
    val localMap = localNotes.associateBy { it.serverId }

    serverNotes.forEach { serverNote ->
        val localNote = localMap[serverNote.serverId]
        if (localNote == null || serverNote.updateTime > localNote.updateTime) {
            noteDao.insertNote(serverNote)  // 服务端更新
        } else if (localNote.updateTime > serverNote.updateTime) {
            // 本地更新,上传到服务端
            uploadNote(localNote)
        }
    }
}

Q2:DataStore + Room 读写冲突怎么处理?

A:两者本身都有完善的线程安全机制,配合架构规范可完全避免冲突:

机制 说明
Room 线程安全 数据库操作串行执行,内置事务锁,多协程并发写入安全
DataStore 串行写入 所有 edit 操作按顺序排队执行,保证写入原子性
读写分离 读取通过响应式 Flow,写入通过挂起函数,互不阻塞
结构化并发 所有操作在 viewModelScope 内执行,页面销毁自动取消

职责分离原则

  • DataStore 负责「元数据」(同步时间戳、登录状态、用户偏好)
  • Room 负责「业务数据」(笔记内容、列表)
  • 两者存储不同维度的数据,互不干扰

Q3:网络状态监听如何精准实现?

A:推荐使用 ConnectivityManager.NetworkCallback 封装为 Flow:

kotlin 复制代码
class NetworkMonitor(private val context: Context) {

    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    val networkStatusFlow: Flow<Boolean> = callbackFlow {
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                trySend(true)
            }
            override fun onLost(network: Network) {
                trySend(false)
            }
        }
        connectivityManager.registerDefaultNetworkCallback(callback)
        // 发送当前状态
        trySend(isNetworkAvailable())
        awaitClose {
            connectivityManager.unregisterNetworkCallback(callback)
        }
    }.catch { emit(false) }

    private fun isNetworkAvailable(): Boolean {
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    }
}

注意事项

  • 需要 ACCESS_NETWORK_STATE 权限

  • 不能仅判断网络是否连接,还要校验实际可用性

  • 不要在主线程做网络状态轮询,必须用系统回调,减少电量消耗

Q4:离线优先架构中,UI 如何感知数据同步状态?

A:通过 DataStore 存储同步状态 + Flow 驱动 UI:

kotlin 复制代码
// 同步状态密封类
sealed class SyncStatus {
    object Synced : SyncStatus()
    object Syncing : SyncStatus()
    data class Pending(val count: Int) : SyncStatus()
    data class Error(val message: String) : SyncStatus()
}

// ViewModel 中暴露状态
val syncStatus: StateFlow<SyncStatus> = combine(
    repository.unsyncedCount,
    repository.isSyncing
) { count, isSyncing ->
    when {
        isSyncing -> SyncStatus.Syncing
        count > 0 -> SyncStatus.Pending(count)
        else -> SyncStatus.Synced
    }
}.stateIn(...)

// UI 中展示不同状态
binding.tvSyncStatus.text = when (status) {
    is SyncStatus.Synced -> "✅ 已同步"
    is SyncStatus.Syncing -> "🔄 同步中..."
    is SyncStatus.Pending -> "📤 ${status.count} 条待同步"
    is SyncStatus.Error -> "❌ 同步失败"
}

六、架构演进小结

阶段 数据层能力 离线支持
第 1-3 篇 ViewModel + LiveData ❌ 无持久化
第 4 篇 Repository 分层 ❌ 内存数据
第 5-6 篇 Room 数据库 ⚠️ 本地数据可用,但无同步
第 7 篇 DataStore ⚠️ 配置可持久化
本篇 DataStore + Room 整合 ✅ 完整的离线优先架构

至此,我们的架构已经具备了企业级应用的所有核心能力:

  • ✅ UI 层:响应式、生命周期安全
  • ✅ 数据层:结构化数据(Room)+ 轻量配置(DataStore)
  • ✅ 离线优先:本地数据优先,后台同步
  • ✅ 数据一致性:多数据源合并,冲突处理
  • ✅ 依赖管理:手动 DI 容器,易于测试

下篇预告

本篇我们完成了 Room 与 DataStore 的深度整合,搭建了标准的离线优先架构,通过四层数据分层、Flow 多源合并、AppContainer 依赖容器,实现了数据层的规范化、解耦化,解决了无网不可用、数据不一致的核心痛点。

目前架构骨架已经全部搭建完成,下一篇我们将进入完整的项目实战阶段,开发记事本首页,实现 RecyclerView 列表、DiffUtil 局部刷新、实时搜索防抖、多状态页面切换,把前面所有组件的能力落地到真实的界面交互中。


🌟 点赞 + 收藏 + 关注,第一时间收到更新推送

💬 评论区聊聊:你在项目中是如何设计离线优先架构的?本地与云端数据冲突时你是怎么处理的?欢迎留言交流!

相关推荐
ltl2 小时前
AI 原生架构:LLM 时代的系统重构
架构
ltl2 小时前
边缘计算架构:算力下沉的设计挑战
架构
JouYY2 小时前
大模型底层学习(三)-从零训练一个 BPE 分词器
架构·llm·agent
Dawson Zhu3 小时前
工业世界模型——基于AI Agent+数学仿真架构
人工智能·架构·agi
Wang's Blog3 小时前
AI Agent白手起家52: 从零搭建钉钉智能助手——资源准备与核心架构实现
人工智能·架构·钉钉
jufeng13073 小时前
【系列:手搓自主 AI Agent:Hermes 架构原理剖析 · 第 1 篇】
人工智能·python·架构·agent
制造业的搬运工3 小时前
智能窗帘PCB低功耗设计方案:架构要点与设计建议
人工智能·科技·架构·制造·pcb工艺
带娃的IT创业者3 小时前
DeepTutor:当 Agent-Native 架构撞上个性化学习的临界点
学习·架构·ai agent·大模型应用·个性化学习·教育技术·agent-native架构
啷里格啷6 小时前
Linux进程管理完全指南:从基础到云原生编排
后端·架构