Kotlin блок сопрограмм основной поток в Android - PullRequest
1 голос
/ 05 февраля 2020

Я новичок в Kotlin и сопрограммах. У меня есть fun в моей деятельности и внутри него, проверьте User имя пользователя и пароль и, если оно истинно, верните Users объект.
все в порядке. но когда я нажимаю кнопку, моя активность блокируется и я жду ответа Users логин.
Я использую это удовольствие:

private fun checkLogin() : Boolean {           
        runBlocking {
            coroutineScope {
                launch {
                    user = viewModel.getUserAsync(login_username.text.toString(), login_password.text.toString()).await()
                }
            }
            if(user == null){
                return@runBlocking false
            }
            return@runBlocking true
        }
        return false
    }  

Это моя ViewModel:

class LoginViewModel(app: Application) : AndroidViewModel(app) {
    val context: Context = app.applicationContext
    private val userService = UsersService(context)

    fun getUserAsync(username: String, password: String) = GlobalScope.async {
        userService.checkLogin(username, password)
    }
}

UsersService :

class UsersService(ctx: Context) : IUsersService {
        private val db: Database = getDatabase(ctx)
        private val api = WebApiService.create()
        override fun insertUser(user: Users): Long {
            return db.usersDao().insertUser(user)
        }

        override suspend fun checkLogin(username: String, pass: String): Users? {
            return api.checkLogin(username, pass)
        }
    }

    interface IUsersService {
        fun insertUser(user: Users) : Long
        suspend fun checkLogin(username: String, pass: String): Users?
    }

И это мой apiInterface:

interface WebApiService {

    @GET("users/login")
    suspend fun checkLogin(@Query("username") username: String,
                   @Query("password")password: String) : Users

Как я могу решить проблему блокировки своей активности при ожидании получения данных с сервера?

Ответы [ 2 ]

2 голосов
/ 05 февраля 2020

Никогда не следует использовать runBlocking в приложении Android. Он предназначен только для использования в функции main приложения JVM или в тесте, позволяющем использовать сопрограммы, которые завершаются до выхода из приложения. В противном случае он побеждает назначение сопрограмм, потому что он блокирует, пока не вернется вся его лямбда.

Вы также не должны использовать GlobalScope, потому что усложняет отмену ваших заданий, когда действие закрывается, и запускает сопрограмма в фоновом потоке вместо основного потока. Вы должны использовать локальную область действия для Деятельности. Вы можете сделать это, создав свойство в своей деятельности (val scope = MainScope()) и отменив его в onDestroy() (scope.cancel()). Или, если вы используете библиотеку androidx.lifecycle:lifecycle-runtime-ktx, вы можете просто использовать существующее свойство lifecycleScope.

И если вы всегда await свою асин c работу до возврата, то вся ваша функция будет блокироваться, пока вы получить результат, поэтому вы взяли фоновое задание и заблокировали основной поток.

Существует несколько способов go об исправлении этого.

  1. Сделайте 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
}
0 голосов
/ 05 февраля 2020

Я знаю, что интернет / извлечение Wi-Fi или пост должны быть в некотором роде фона / асинхронной задачи. Вы пытались использовать @ Background / @ Uithread из Android Аннотации. Это потребует, чтобы вы поместили некоторые зависимости в gradle. Но это один из способов, которым я имел дело со службами.

Вот исходная ссылка для оригинального DO C этого https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background

...