Repository 模式
问题
Repository 模式在 Android 架构中起什么作用?如何实现?
答案
Repository 的定位
Repository 是数据层的统一入口,屏蔽数据来源的细节:
离线优先策略
class ArticleRepository(
private val api: ArticleApi,
private val dao: ArticleDao
) {
// 离线优先:先返回本地 → 后台更新 → 发射最新数据
fun getArticles(): Flow<List<Article>> = flow {
// 1. 先发射本地数据
val local = dao.getAll()
emit(local)
// 2. 请求网络数据
try {
val remote = api.getArticles()
dao.insertAll(remote)
// 3. 发射最新数据
emit(dao.getAll())
} catch (e: IOException) {
// 网络失败,本地数据已经发射过了
if (local.isEmpty()) throw e
}
}
// 使用 Room Flow 自动监听变化(推荐)
fun observeArticles(): Flow<List<Article>> {
// Room Flow 在数据变化时自动发射
refreshArticles()
return dao.observeAll()
}
private fun refreshArticles() {
CoroutineScope(Dispatchers.IO).launch {
try {
val remote = api.getArticles()
dao.insertAll(remote)
// Room Flow 会自动发射新数据
} catch (_: Exception) { }
}
}
}
单一数据源原则(Single Source of Truth)
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
// Room 作为唯一数据源
val users: Flow<List<User>> = dao.observeAll()
suspend fun refresh() {
val remoteUsers = api.getUsers()
dao.deleteAllAndInsert(remoteUsers)
// Room Flow 自动通知 UI 更新
}
}
Single Source of Truth
本地数据库(Room)作为唯一数据源,网络数据写入数据库后,UI 通过 Room Flow 获取最新数据。这避免了缓存不一致和数据冲突问题。
常见面试问题
Q1: Repository 应该是单例还是多实例?
答案:
通常是单例(通过 Hilt @Singleton 注入),因为 Repository 管理数据源和内存缓存,多实例会导致缓存不一致。单例保证同一数据的请求共享缓存和连接池。
Q2: Repository 如何处理并发请求?
答案:
对于相同请求的并发场景(如多个 Fragment 同时请求用户数据),使用 Mutex 或 SharedFlow 避免重复请求:
class UserRepository {
private val userCache = MutableStateFlow<User?>(null)
private val mutex = Mutex()
suspend fun getUser(): User {
userCache.value?.let { return it }
return mutex.withLock {
userCache.value ?: api.getUser().also { userCache.value = it }
}
}
}