Android上Kotlin流的存储库级内存缓存



假设您在Android应用程序中有一个从远程数据源下载的用户列表,并且由于某种原因您没有本地数据库。然后,这个用户列表在多个ViewModel中用于整个应用程序,以发出其他网络请求,因此,只要应用程序存在,您肯定希望将其缓存,并仅在需要时重新获取它。这必然意味着您希望将其缓存在数据层中在我的情况下,这是一个Repository,然后从你的ViewModels中获得它。
ViewModel这样的状态持有人中很容易做到-只需制作StateFlow或其他任何东西。但是,如果我们想要一个FlowList<User>(在每个API请求后缓存在RAM中)在存储库中可用,然后从UI层从中收集,该怎么办?什么最可测试稳定,实现这一目标的方法?
我最初的想法是:

class UsersRepository @Inject constructor(
private val usersApi: UsersApi,
private val handler: ResponseHandler
) {
private val _usersFlow = MutableStateFlow<Resource<List<UserResponse>>>(Resource.Empty)
val usersFlow = _usersFlow.asStateFlow()
suspend fun fetchUserList() = withContext(Dispatchers.IO) {
_usersFlow.emit(Resource.Loading)
_usersFlow.emit(
handler {
usersApi.getUsers()
}
)
}
}

其中ResponseHandler为:

class ResponseHandler {
suspend operator fun <T> invoke(block: suspend () -> T) = try {
Resource.Success(block())
} catch (e: Exception) {
Log.e(javaClass.name, e.toString())
val errorCode = when (e) {
is HttpException -> e.code()
is SocketTimeoutException -> ErrorCodes.SocketTimeOut.code
is UnknownHostException -> ErrorCodes.UnknownHost.code
else -> Int.MAX_VALUE
}
Resource.Error(getErrorMessage(errorCode))
}
}

但是在研究的过程中,我在网上随机发现一个人告诉我这是错误的:

目前StateFlow本质上是热的,所以不建议在存储库中使用。对于冷流和响应流,您可以在存储库中使用flow、channelFlow或callbackFlow。

他对吗?如果他是,那么在这种情况下,冷流到底有什么帮助,我们又该如何恰当地管理它们?

如果有帮助的话,我的UI层是单独用Jetpack Compose编写的

在官方的"应用架构指南"中;from Google for Android:

关于true来源:✅存储库可以包含内存内缓存。

事实的来源可以是数据源(例如数据库),甚至可以是存储库可能包含的内存缓存. 存储库将不同的数据源组合在一起,并解决数据源之间的任何潜在冲突,以定期或由于用户输入事件而更新单个事实源。

关于:✅您可以将存储库的实例限定在Application类中(但要小心)。

如果一个类包含内存中的数据(例如,您可能需要的缓存)的特定时期重用该类的相同实例时间。这也被称为类实例的生命周期。

如果类的职责对整个应用程序至关重要,您可以将该类的实例限定在Application类的范围内。这使实例遵循应用的生命周期。

关于实现:我建议你直接查看链接

class NewsRepository(
private val newsRemoteDataSource: NewsRemoteDataSource
) {
// Mutex to make writes to cached values thread-safe.
private val latestNewsMutex = Mutex()
// Cache of the latest news got from the network.
private var latestNews: List<ArticleHeadline> = emptyList()
suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
if (refresh || latestNews.isEmpty()) {
val networkResult = newsRemoteDataSource.fetchLatestNews()
// Thread-safe write to latestNews
latestNewsMutex.withLock {
this.latestNews = networkResult
}
}
return latestNewsMutex.withLock { this.latestNews }
}
}

你应该阅读下面的页面,我想它会回答你的很多问题:https://developer.android.com/topic/architecture/data-layer

要使其作为缓存工作,您必须将此存储库作为单例使用。这实际上造成了巨大的内存泄漏,因为您无法控制这些内存。你不能释放它,你不能绕过缓存,如果你想(我的意思是你可以,但它需要额外的代码流之外),你没有任何控制的驱逐。这是一个非常愚蠢的缓存,就像内存泄漏一样。不值得。

冷流不"帮助";在缓存本身。它们只是让您控制来自客户端的每个请求。如果条目被缓存,您可以在那里检查一些外部内存缓存。如果是,这是正确的还是应该被驱逐?如果它被驱逐,你可以只是一个正常的请求。所有这些都是一个单独的流,之后被立即处理,所以没有内存泄漏。唯一必须是单例的部分是缓存。虽然您可以将其实现为磁盘缓存,但无论如何它都会比网络快。

最新更新