【问题标题】:In-memory caching on repository level for Kotlin Flows on AndroidAndroid 上 Kotlin Flows 的存储库级别的内存缓存
【发布时间】:2022-07-27 17:08:13
【问题描述】:

假设您有一个从您的 Android 应用程序中的远程数据源下载的用户列表,并且由于某种原因您没有本地数据库。然后,此用户列表将在整个应用程序中以多个ViewModels 使用以发出其他网络请求,因此您肯定希望在应用程序存在期间将其缓存并仅在需要时重新获取它。这必然意味着您希望将其缓存在 数据层 中,在我的例子中是 Repository,然后从您的 ViewModels 中获取它。
在像ViewModel 这样的状态持有者中很容易做到 - 只需制作StateFlow 或其他任何东西。但是,如果我们想要在存储库中可用的 List<User>List<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))
    }
}

但是在研究的时候,我在网上发现了一个随机的家伙telling这是错误的:

目前 StateFlow 本质上很热门,因此不建议在存储库中使用。对于冷流和反应流,您可以在 repository 中使用 flow、channelFlow 或 callbackFlow。

他说的对吗?如果是,那么冷流在这种情况下究竟有什么帮助,我们如何妥善管理它们?

如果有帮助,我的 UI 层仅使用 Jetpack Compose 编写

【问题讨论】:

    标签: android mvvm repository android-jetpack-compose kotlin-flow


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      在来自 Google for Android 的官方 "Guide to app architecture" 中:

      About the source of true: ✅ 存储库可以包含内存缓存。

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

      About the lifecycle:✅您可以将存储库的实例限定为 Application 类(但要小心)。

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

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

      About the implementation:建议你直接查看链接。

      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

      【讨论】:

        猜你喜欢
        • 2019-06-27
        • 2016-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-23
        • 2019-03-04
        • 2021-06-12
        相关资源
        最近更新 更多