科特林在安卓系统中协同挡路主线 [英] Kotlin coroutines block main thread in Android

查看:41
本文介绍了科特林在安卓系统中协同挡路主线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是新手,不熟悉柯特林和协程。我的活动中有一个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

如何解决等待从服务器检索数据时阻止活动的问题?

推荐答案

千万不要在安卓应用中使用runBlocking。它只能在JVM应用程序的main函数中使用,或者在允许使用在应用程序退出前完成的协同程序的测试中使用。否则,它会破坏协同例程的目的,因为它会阻塞,直到其所有lambda都返回。

您也不应该使用GlobalScope,因为它使在活动关闭时取消作业变得棘手,并且它在后台线程而不是主线程中启动协程。您应该为活动使用本地作用域。您可以通过在活动(val scope = MainScope())中创建属性并在onDestroy()(scope.cancel())中取消它来完成此操作。或者,如果您使用androidx.lifecycle:lifecycle-runtime-ktx库,则可以只使用现有的lifecycleScope属性。

如果您总是在返回前await您的异步作业,那么您的整个函数将挡路,直到您得到结果,所以您接受了一个后台任务,并将其挡路设为主线程。

有几种方法可以修复此问题。

  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
}
  1. 有了适当的视图模型封装,活动确实不应该像这样启动协程。user属性应该是活动可以观察的ViewModel中的LiveData。因此,只需从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
}

这篇关于科特林在安卓系统中协同挡路主线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆