android - 从 viewModelScope 中的 Flow 收集数据是否会阻止 Android Studio 中的 UI?

标签 android kotlin kotlin-coroutines

代码A来自官方article关于流量viewModelScope.launch{}默认在 UI 线程中运行,我认为 suspend fun fetchLatestNews()默认情况下也会在 UI 线程中运行,所以我认为代码 A 可能会在 fetchLatestNews() 时导致 UI 阻塞是长时间的操作吧?
我认为代码 B 可以解决问题,对吧?
代码 A

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    init {
        viewModelScope.launch {
            // Trigger the flow and consume its elements using collect
            newsRepository.favoriteLatestNews.collect { favoriteNews ->
                // Update View with the latest favorite news
            }
        }
    }
}



class NewsRemoteDataSource(
    private val newsApi: NewsApi,
    private val refreshIntervalMs: Long = 5000
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        while(true) {
            val latestNews = newsApi.fetchLatestNews()
            emit(latestNews) // Emits the result of the request to the flow
            delay(refreshIntervalMs) // Suspends the coroutine for some time
        }
    }
}

// Interface that provides a way to make network requests with suspend functions
interface NewsApi {
    suspend fun fetchLatestNews(): List<ArticleHeadline>
}
代码 B
class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    init {
        viewModelScope.launch(Dispatchers.IO) {
           //The same
        }
    }
}


//The same
新增内容:
致 Tenfour04:谢谢!
我认为挂起功能可能会阻塞 UI,所以我想定义 dispatchers为了不阻塞UI,最重要的是!这样对吗?
如果我使用 Dispatchers.IO,当我单击“开始”按钮显示信息时,代码 1 运行良好.
如果我使用 Dispatchers.Main,当我单击“开始”按钮时,代码 2 被卡住并且没有信息更新.
如果我使用 Dispatchers.Main,代码 3 可以像代码 1 一样工作,原因只是因为我设置了delay(100) .
顺便说一句,Flow 已暂停。所以如果有很长时间的操作,即使它是用协程而不是 soundDbFlow().collect { myInfo.value = it.toString() } 包装的, 我想我可以得到和 Code 1, Code 2 和 Code 3 一样的测试结果。
更重要的是,在我添加 flowOn(Dispatchers.IO) 后,代码 4 就可以了对于 Flow,即使它是在 viewModelScope.launch(Dispatchers.Main){} 中启动的!
代码 1 确定
class HandleMeter: ViewModel() {
    var myInfo = mutableStateOf("World")
    private var myJob: Job?=null
    private var k=0

    private fun soundDbFlow() = flow {
          while (true) {
             emit(k++)
             delay(0)
          }
   }

    fun calCurrentAsynNew() {
        myJob?.cancel()
        myJob = viewModelScope.launch(Dispatchers.IO){
            soundDbFlow().collect { myInfo.value = it.toString() }
        }
    }

    fun cancelJob(){
        myJob?.cancel()
    }

}


@Composable
fun Greeting(handleMeter: HandleMeter) {
    var info = handleMeter.myInfo
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Hello ${info.value}")

        Button(
            onClick = { handleMeter.calCurrentAsynNew() }
        ) {
            Text("Start")
        }

        Button(
            onClick = { handleMeter.cancelJob() }
        ) {
            Text("Stop")
        }
    }
}
代码 2 卡住
class HandleMeter: ViewModel() {
   private fun soundDbFlow() = flow {
          while (true) {
             emit(k++)
             delay(0)
          }
   }

    fun calCurrentAsynNew() {
        myJob?.cancel()
        myJob = viewModelScope.launch(Dispatchers.Main){
            soundDbFlow().collect { myInfo.value = it.toString() }
        }
    }
    ...
   //The same

}
...
//The same
代码 3 确定
class HandleMeter: ViewModel() {
   private fun soundDbFlow() = flow {
          while (true) {
             emit(k++)
             delay(100)  //It's 100
          }
   }

    fun calCurrentAsynNew() {
        myJob?.cancel()
        myJob = viewModelScope.launch(Dispatchers.Main){
            soundDbFlow().collect { myInfo.value = it.toString() }
        }
    }
    ...
    //The same

}
...
//The same
代码 4 确定
class HandleMeter: ViewModel() {
   private fun soundDbFlow() = flow {
        while (true) {
            emit(k++)
            delay(0)
        }
    }.flowOn(Dispatchers.IO)// I added

    fun calCurrentAsynNew() {
        myJob?.cancel()
        myJob = viewModelScope.launch(Dispatchers.Main){
            soundDbFlow().collect { myInfo.value = it.toString() }
        }
    }

    ...
    //The same

}
...
//The same

最佳答案

挂起函数不会阻塞,除非它违反了挂起函数永远不能阻塞的约定。因此,从主调度程序调用您的协程并不重要。调用fetchLatestNews()不会阻塞主线程, 除非你不正确地组合了函数的实现,以至于它实际上阻塞了。
您通常不需要像在 中那样执行此操作。代码 B :

viewModelScope.launch(Dispatchers.IO) {
因为您通常不会在协程的顶层调用阻塞函数。如果你是,你可以用 withContext(Dispatchers.IO) { } 包裹这些 fragment .将协程留在 Main 调度程序上通常更方便,因为 Android 中有很多非挂起函数需要您从主线程调用它们。如果你翻转它,你可能需要withContext(Dispatchers.Main) { }在比你需要的地方更多的地方,你也会在协程实际开始之前产生一帧延迟。此外,如果您的协程与 ViewModel 中的属性交互,如果您只从 Main 调度程序中触摸它们,您可以避免并发访问属性的潜在问题,因为它是单线程的。
可能存在一些异常(exception)情况,即您启动的协程不与任何此类 Main 必需的函数交互并直接调用阻塞函数,但我认为这应该很少见,特别是如果您练习良好的封装 (see here)。如果你将协程顶层的一段代码分解成它自己的函数,你可以把那个单独的函数变成一个使用 withContext(Dispatchers.IO) 的挂起函数。如有必要。然后你的顶级协程会看起来很干净。

关于android - 从 viewModelScope 中的 Flow 收集数据是否会阻止 Android Studio 中的 UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70891060/

相关文章:

android - 带有反色和深色模式的通知

java - 检测 Activity 何时发生变化

java - 数据类 Kotlin 到 Java 类

Kotlin 作为协程之间的消息队列流动

kotlin - 在 Kotlin 中锁定互斥锁的正确方法

android - Firebase 测试实验室中的仪器测试不要求用户权限

android - 将一个 mutableList 从 Activity A 传递到 Kotlin 中的 Activity B。

kotlin - 为什么 Kotlin 不遵循 Java 语法?

android - 协程中的 Job.Cancel 与 Scope.Cancel 有什么区别?

java - 不知道如何在Java中使用wait()和notify()