LifecycleScope.launchWhenStarted 安全吗?如果不安全,在什么情况下?

oiy*_*yio 6 android kotlin kotlin-coroutines kotlin-flow

在我的应用程序中,我使用协程和流程向 API 发送请求。当请求到来时,我更改了 stateFlow 的值,以便我的活动中的收集器看到它并完成其工作。这是一个简单的场景。在android网站(https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)中,建议了2种方法。在这种情况下,我应该选择哪种方法?

以下是上述链接的引用。:

使用 launchWhen() 函数可以安全地收集 StateFlow,因为它们的作用域为 ViewModel,这使得它们在 View 转到后台时保留在内存中,并且它们只需通知 View 有关 UI 状态即可完成轻量级工作。然而,问题可能出在其他从事更密集工作的生产商身上。

在这段引用中,据说第一种方法是安全的,但是在最后一句中有一个警告。这个警告有什么用?我不明白这是否安全。制作人是否继续以第一种方式工作?就我而言,如果请求尚未到来,并且用户在后台运行应用程序,那么生产者是否会工作并尝试更新 UI?

第一种方法:

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // This coroutine will run the given block when the lifecycle
        // is at least in the Started state and will suspend when
        // the view moves to the Stopped state
        lifecycleScope.launchWhenStarted {
            // Triggers the flow and starts listening for values
            latestNewsViewModel.uiState.collect { uiState ->
                // New value received
                when (uiState) {
                    is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                    is LatestNewsUiState.Error -> showError(uiState.exception)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第二种方法:

class LatestNewsActivity : AppCompatActivity() {
    ...
    // Coroutine listening for UI states
    private var uiStateJob: Job? = null

    override fun onStart() {
        super.onStart()
        // Start collecting when the View is visible
        uiStateJob = lifecycleScope.launch {
            latestNewsViewModel.uiState.collect { uiState -> ... }
        }
    }

    override fun onStop() {
        // Stop collecting when the View goes to the background
        uiStateJob?.cancel()
        super.onStop()
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种非常简单的情况下哪种方法更合适?

Ant*_*nio 5

更新(2023 年 3 月)

Lifecycle.launchWhenX 方法和 Lifecycle.whenX 方法已被弃用,因为使用暂停调度程序在某些情况下可能会导致资源浪费。建议使用Lifecycle.repeatOnLifecycle。请参阅生命周期版本 2.6.0 发行说明。

原答案

无论如何,collect如果视图处于停止状态或任何小于 STARTED 的状态,则该块将不起作用。

但是有一个称为流量订阅计数的东西,它表明有多少订阅者希望收集流量。窍门来了。当视图移动到小于 STARTED 状态的状态时,collect块将被挂起,但流的订阅计数保持不变,这将流生产者保留在内存中。但是,如果您通过取消作业来采用第二种方法,那么当您取消作业时,订阅计数将会减少,并且它将阻止流生产者在内存中,这在不需要时会浪费资源。

现在,他们在最后一句话中给出的警告是针对除 之外的其他流生产者的StateFlow。例如流程构建器功能flow{ //some intensive work }。StateFlow 的工作方式有所不同,因为它只是将 UI 状态发送到活动/片段,并且不包含任何代码块(昂贵的代码)。因此,使用 launchWhen 函数收集 StateFlow 是安全的。

这是 UI 状态发射情况下所需的工作,因为即使片段位于后台堆栈中且不活动,您也需要流程来发出更改,但一旦再次从后台堆栈恢复,就应该立即获取最新更改。这里 StateFlow 应保留在内存中,其范围仅限于视图模型。