设计持久化层
问题
如何设计 Android 客户端的数据持久化层?
答案
分层架构
离线优先策略(Single Source of Truth)
// Repository 实现离线优先
class ArticleRepository(
private val api: ArticleApi,
private val dao: ArticleDao
) {
// Room 数据库作为 Single Source of Truth
fun getArticles(): Flow<List<Article>> = dao.getAllArticles()
// 从网络刷新数据,写入数据库
suspend fun refresh(): Result<Unit> = try {
val articles = api.getArticles()
dao.upsertAll(articles) // 写入数据库
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
// ViewModel
class ArticleViewModel(
private val repository: ArticleRepository
) : ViewModel() {
// UI 始终观察数据库数据
val articles = repository.getArticles()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun refresh() {
viewModelScope.launch {
_isRefreshing.value = true
repository.refresh()
_isRefreshing.value = false
}
}
}
存储方案选型
| 数据类型 | 推荐方案 | 原因 |
|---|---|---|
| 用户设置 | DataStore | 类型安全、协程支持 |
| 结构化数据 | Room | SQL 查询、关系映射 |
| 缓存K-V | MMKV | 高性能、跨进程 |
| 文件、图片 | File + 内部存储 | 权限隔离 |
| 临时缓存 | LruCache + DiskLruCache | 自动淘汰 |
数据库版本迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE articles ADD COLUMN author TEXT NOT NULL DEFAULT ''")
}
}
val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.build()
常见面试问题
Q1: 什么是 Single Source of Truth?为什么推荐这种模式?
答案:
Single Source of Truth(SSOT)模式中,UI 只观察本地数据库(Room),网络数据先写入数据库再由数据库通知 UI 更新。好处是:
- 离线可用:无网络时展示本地数据
- 一致性:所有数据来源统一,避免 UI 显示的是缓存数据而非最新数据
- 简化逻辑:UI 层只需观察一个数据源(Flow/LiveData),无需处理网络和数据库的合并