Kotlin Android - 分派到主线程的正确方法
Kotlin Android - correct way to dispatch to main thread
我在 androidx.lifecycle.ViewModel
中使用 OkHttp 库从互联网下载一些数据
然后我想更新我的 LiveData
。似乎从后台线程执行它会抛出这样的异常:
2022-01-17 15:47:59.589 7354-7396/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.example.myapplication, PID: 7354
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat.invoke(MainActivityViewModel.kt:86)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat.invoke(MainActivityViewModel.kt:39)
at com.example.myapplication.singleton.CommunicationManager$sendRequest.onResponse(CommunicationManager.kt:24)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
现在我发现了两种不同的方式来从 ViewModel
分派到主线程(根据 AAC 指南,它没有引用 Context),请参见此处:
GlobalScope.launch {
withContext(Dispatchers.Main) {
// do whatever, e.g. update LiveData
}
}
或
Handler(Looper.getMainLooper()).post(Runnable {
// do whatever, e.g. update LiveData
})
哪个是正确的方法?也就是说,在运行时影响最小。
更新 我确实发现我也可以做 myLiveData.post()
并且它在后台线程中工作。
不过,我想知道在 kotlin
下现代 Android 中将工作分派到主线程的正确方法是什么
有很多方法可以做到这一点,您可以简单地 post 为实时数据赋值,使用主线程上 运行 的调度程序和处理程序,因为您提供主线程的循环程序。
另一种方法是您可以使用高阶函数来更新视图模型,这很容易使用并尝试一下。
内部视图模型,
private val _downloading = MutableLiveData<Result<Boolean>>()
val downloading: LiveData<Result<Boolean>>
get() = _downloading
fun downloadFile() {
viewModelScope.launch {
try {
_downloading.value = Result.Loading
val result = withContext(Dispatchers.IO) {
// download something
}
_downloading.value = Result.Success(true)
} catch (ex: Exception) {
_downloading.value = Result.Failure(ex)
}
}
}
在activity/fragment,
viewModel.downloading.observe(this, {
when (it) {
is Result.Failure -> TODO()
Result.Loading -> TODO()
is Result.Success -> TODO()
}
})
Result
是一个密封的class来捕获状态,这反过来会帮助我们相应地更新UI。还使用 viewmodelscope
而不是 GlobalScope
因为我们不希望在视图模型被销毁时继续下载。
使用LivaData
将工作从后台线程分派到主线程的正确方法是使用LivaData.postValue()
方法。它将任务发布到主线程以设置给定值。
另一种方法是在ViewModel
class中使用viewModelScope
扩展属性,默认情况下它使用Dispatchers.Main
上下文执行协程,这意味着你可以在这样的协程中更新 UI 。例如,在您的 ViewModel
class:
viewModelScope.launch {
val result = makeNetworkCall()
// use result to update UI
liveData.value = result
}
// withContext - switches context to background thread
suspend fun makeNetworkCall(): String = withContext(Dispatchers.IO) {
delay(1000) // simulate network call
"SomeResult"
}
要使用的依赖性 viewModelScope
:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
GlobalScope
is highly discouraged to use, it can only be used in specific cases, .
我在 androidx.lifecycle.ViewModel
中使用 OkHttp 库从互联网下载一些数据
然后我想更新我的 LiveData
。似乎从后台线程执行它会抛出这样的异常:
2022-01-17 15:47:59.589 7354-7396/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.example.myapplication, PID: 7354
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat.invoke(MainActivityViewModel.kt:86)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat.invoke(MainActivityViewModel.kt:39)
at com.example.myapplication.singleton.CommunicationManager$sendRequest.onResponse(CommunicationManager.kt:24)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
现在我发现了两种不同的方式来从 ViewModel
分派到主线程(根据 AAC 指南,它没有引用 Context),请参见此处:
GlobalScope.launch {
withContext(Dispatchers.Main) {
// do whatever, e.g. update LiveData
}
}
或
Handler(Looper.getMainLooper()).post(Runnable {
// do whatever, e.g. update LiveData
})
哪个是正确的方法?也就是说,在运行时影响最小。
更新 我确实发现我也可以做 myLiveData.post()
并且它在后台线程中工作。
不过,我想知道在 kotlin
下现代 Android 中将工作分派到主线程的正确方法是什么有很多方法可以做到这一点,您可以简单地 post 为实时数据赋值,使用主线程上 运行 的调度程序和处理程序,因为您提供主线程的循环程序。
另一种方法是您可以使用高阶函数来更新视图模型,这很容易使用并尝试一下。
内部视图模型,
private val _downloading = MutableLiveData<Result<Boolean>>()
val downloading: LiveData<Result<Boolean>>
get() = _downloading
fun downloadFile() {
viewModelScope.launch {
try {
_downloading.value = Result.Loading
val result = withContext(Dispatchers.IO) {
// download something
}
_downloading.value = Result.Success(true)
} catch (ex: Exception) {
_downloading.value = Result.Failure(ex)
}
}
}
在activity/fragment,
viewModel.downloading.observe(this, {
when (it) {
is Result.Failure -> TODO()
Result.Loading -> TODO()
is Result.Success -> TODO()
}
})
Result
是一个密封的class来捕获状态,这反过来会帮助我们相应地更新UI。还使用 viewmodelscope
而不是 GlobalScope
因为我们不希望在视图模型被销毁时继续下载。
使用LivaData
将工作从后台线程分派到主线程的正确方法是使用LivaData.postValue()
方法。它将任务发布到主线程以设置给定值。
另一种方法是在ViewModel
class中使用viewModelScope
扩展属性,默认情况下它使用Dispatchers.Main
上下文执行协程,这意味着你可以在这样的协程中更新 UI 。例如,在您的 ViewModel
class:
viewModelScope.launch {
val result = makeNetworkCall()
// use result to update UI
liveData.value = result
}
// withContext - switches context to background thread
suspend fun makeNetworkCall(): String = withContext(Dispatchers.IO) {
delay(1000) // simulate network call
"SomeResult"
}
要使用的依赖性 viewModelScope
:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
GlobalScope
is highly discouraged to use, it can only be used in specific cases,