MutableLiveData: 无法在来自 Coroutine 的后台线程上调用 setValue

我正试图通过协同程序触发 LiveData 的更新:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
AddressList.value = getAddressList()
}
return AddressList
}

但我得到了以下错误:

IllegalStateException: 无法在后台线程上调用 setValue

有没有办法让它与协同程序一起工作?

58128 次浏览

我刚刚发现使用 withContext(Dispatchers.Main){}是可行的:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
GlobalScope.launch {
withContext(Dispatchers.Main){ AddressList.value = getAddressList() }
}
return AddressList
}

您可以执行下列操作之一:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
AddressList.postValue(getAddressList())
}


return AddressList
}

或者

fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
val adresses = getAddressList()
withContext(Dispatchers.Main) {
AddressList.value = adresses
}
}
return AddressList
}

尽管其他人已经指出,在这种情况下,库提供了自己的方法来将操作发布到主线程,但协程提供了一种通用的解决方案,不管给定库的功能如何都能正常工作。

第一步是停止使用 GlobalScope作为后台作业,这样做将导致泄漏,您的活动、计划作业或您从中调用此作业的任何工作单元可能会被破坏,但您的作业将继续在后台执行,甚至将其结果提交给主线程。关于 GlobalScope的正式文件是这么说的:

应用程序代码通常应该使用应用程序定义的 CoroutineScope,不建议在 GlobalScope 实例上使用异步或启动。

您应该定义自己的协同程序范围,其 coroutineContext属性应该包含 Dispatchers.Main作为调度程序。此外,在函数调用中启动作业并返回 LiveData(基本上是另一种 Future)的整个模式并不是使用协程的最方便的方式。相反,你应该

suspend fun getAddresses() = withContext(Dispatchers.Default) { getAddressList() }

在调用站点,你应该使用一个协程 launch,在这个协程中,你可以自由地调用 getAddresses(),就像它是一个阻塞方法一样,直接获取地址作为返回值。

在我的例子中,我不得不将 调度员,总部添加到发射参数中,它工作得很好:

 val job = GlobalScope.launch(Dispatchers.Main) {
delay(1500)
search(query)
}

如果您想使用 协奏曲更新 UI,有两种方法可以实现

启动(Dispatchers. Main) :

GlobalScope.launch(Dispatchers.Main) {
delay(1000)     // 1 sec delay
// call to UI thread
}

如果你想在后台完成一些工作,但之后你想 更新用户界面,这可以通过以下方法实现:

WithContext (Dispatchers. Main)

GlobalScope.launch {
delay(1000)     // 1 sec delay


// do some background task


withContext(Dispatchers.Main) {
// call to UI thread
}
}

使用 liveData.postValue(value)而不是 liveData.value = value

来自 文件:

PostValue -将任务发送到主线程以设置给定的值。

当我为一个测试用例调用 runBlockingTest 中的协程时,我正面临这个错误

TestCoroutineRule().runBlockingTest {


}

我通过添加 instExecutorRule 作为类成员修复了这个问题

@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()

下面的代码可以帮助我更新线程中的实时数据:

 val adresses = getAddressList()
GlobalScope.launch {
messages.postValue(adresses)
}