Никогда не следует использовать runBlocking
в приложении Android. Он предназначен только для использования в функции main
приложения JVM или в тесте, позволяющем использовать сопрограммы, которые завершаются до выхода из приложения. В противном случае он побеждает назначение сопрограмм, потому что он блокирует, пока не вернется вся его лямбда.
Вы также не должны использовать GlobalScope, потому что усложняет отмену ваших заданий, когда действие закрывается, и запускает сопрограмма в фоновом потоке вместо основного потока. Вы должны использовать локальную область действия для Деятельности. Вы можете сделать это, создав свойство в своей деятельности (val scope = MainScope()
) и отменив его в onDestroy()
(scope.cancel()
). Или, если вы используете библиотеку androidx.lifecycle:lifecycle-runtime-ktx
, вы можете просто использовать существующее свойство lifecycleScope
.
И если вы всегда await
свою асин c работу до возврата, то вся ваша функция будет блокироваться, пока вы получить результат, поэтому вы взяли фоновое задание и заблокировали основной поток.
Существует несколько способов go об исправлении этого.
- Сделайте ViewModel предоставляет функцию приостановки, и действие вызывает ее из сопрограммы.
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
// withContext(Dispatchers.Default) makes the suspend function do something
// on a background thread and resumes the calling thread (usually the main
// thread) when the result is ready. This is the usual way to create a simple
// suspend function. If you don't delegate to a different Dispatcher like this,
// your suspend function runs its code in the same thread that called the function
// which is not what you want for a background task.
suspend fun getUser(username: String, password: String) = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
}
//In your activity somewhere:
lifecycleScope.launch {
user = viewModel.getUser(login_username.text.toString(), login_password.text.toString())
// do something with user
}
При правильной инкапсуляции viewmodel Activity действительно не должно запускать сопрограммы, подобные этой. Свойство
user
должно быть LiveData во ViewModel, которое может наблюдать действие. Тогда сопрограммы нужно запускать только из ViewModel:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
init {
fetchUser()
}
private fun fetchUser(username: String, password: String) = viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
_user.value = result
}
}
//In your activity somewhere:
viewModel.user.observe(this) { user ->
// do something with user
}