我是否需要在@Composable 中使用 repeatOnLifecycle 扭曲热流的 collectAsState()?

Do I need to warp the collectAsState() of a hot Flow with repeatOnLifecycle in @Composable?

我已阅读文章A safer way to collect flows from Android UIs

我知道以下内容。

由通道支持的冷流或使用带有缓冲区的运算符(例如 buffer、conflate、flowOn 或 shareIn)使用某些现有 API(例如 CoroutineScope.launch 收集是不安全的,Flow.launchIn,或者LifecycleCoroutineScope.launchWhenX,除非你在activity进入后台时手动取消启动协程的Job。这些 API 将保持底层流生产者处于活动状态,同时在后台将项目发送到缓冲区,从而浪费资源。

代码A来自官方样本project

viewModel.suggestedDestinations是一个MutableStateFlow,很火爆。

不知道热流的操作collectAsState()在@Composable中是否安全UI.

1:我是否需要像代码 B 或代码 C 那样使用代码来替换代码 A 以获得 热流

2: cold Flow 的操作collectAsState() 在@Composable UI.

中是否安全

代码A

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CraneHomeContent(
    onExploreItemClicked: OnExploreItemClicked,
    openDrawer: () -> Unit,
    modifier: Modifier = Modifier,
    viewModel: MainViewModel = viewModel(),
) {
    val suggestedDestinations by viewModel.suggestedDestinations.collectAsState()

    ...
    
}


@HiltViewModel
class MainViewModel @Inject constructor(
    ...
) : ViewModel() {
    ...
    private val _suggestedDestinations = MutableStateFlow<List<ExploreModel>>(emptyList())
    val suggestedDestinations: StateFlow<List<ExploreModel>>
}

代码B

class LocationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)      
        lifecycleScope.launch {        
            repeatOnLifecycle(Lifecycle.State.STARTED) {               
               ...
            }
        }
    }
}

代码C

@Composable
fun LocationScreen(locationFlow: Flow<Flow>) {
   val lifecycleOwner = LocalLifecycleOwner.current
   val locationFlowLifecycleAware = remember(locationFlow, lifecycleOwner) {
        locationFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
     }
   val location by locationFlowLifecycleAware.collectAsState()    
     ...
}

collectAsState(代码 A)对任何类型的 Flow 都是安全的(cold/hot 没关系)。如果你看看 collectAsState 是如何实现的,那么你会发现它在深处使用了 LaunchedEffect (collectAsState -> produceState -> LaunchedEffect)

internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel()
        job = null
    }

    override fun onAbandoned() {
        job?.cancel()
        job = null
    }
}

它创建一个协程范围并在进入组合后启动 task lambda,并在它离开组合后自动取消它。

在代码 A 中,只要 CraneHomeContent 被其他代码调用,viewModel.suggestedDestinations.collectAsState()(连同它的 LaunchedEffect 和协程作用域)就会处于活动状态。一旦 CraneHomeContent 停止被调用, collectAsState() 内部的 LaunchedEffect 就会被取消(协程范围也是如此)。

如果从多个地方调用它,那么会有多个 LaunchedEffect,因此会有多个协程范围。