android - Kotlin 协程阻塞 Android 中的主线程

标签 android kotlin retrofit

我是 Kotlin 和协程的新手。我有一个 fun在我的 Activity 和里面,检查User用户名和密码,如果为真,返回Users目的。
一切都好。但是当我按下按钮时,我的 Activity 被阻止并等待 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)
    }
}

用户服务:
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

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

最佳答案

你不应该使用 runBlocking在 Android 应用程序中。它仅用于 main JVM 应用程序的功能或在测试中允许使用在应用程序退出之前完成的协程。否则它会破坏协程的目的,因为它会阻塞,直到它的所有 lambda 返回。

您也不应该使用 GlobalScope,因为当 Activity 关闭时取消您的作业会很棘手,并且它会在后台线程而不是主线程中启动协程。您应该为 Activity 使用本地范围。您可以通过在您的 Activity ( val scope = MainScope() ) 中创建一个属性并在 onDestroy() 中取消它来做到这一点。 (scope.cancel())。或者,如果您使用 androidx.lifecycle:lifecycle-runtime-ktx库,您可以使用现有的 lifecycleScope属性(property)。

如果你总是 await在返回之前你的异步作业,那么你的整个函数将阻塞,直到你得到结果,所以你已经完成了一个后台任务并让它阻塞了主线程。

有几种方法可以解决这个问题。

  • 让 ViewModel 公开一个挂起函数, Activity 从协程中调用它。
  • 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
    }
    
  • 通过适当的 View 模型封装,Activity 真的不应该像这样启动协程。 user属性应该是 Activity 可以观察到的 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
    }
    

    关于android - Kotlin 协程阻塞 Android 中的主线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60081995/

    相关文章:

    android - 应用程序关闭时的 Firebase 通知(问题)

    java - 您在 `removeAnnotationIcon` 被销毁后调用 `MapView` ,您是在 `onDestroy()` 之后调用它吗? map 框

    android - 如何管理多个android模块之间的改造

    java - 无法获取 Retrofit 2 响应正文

    java - 无法显示 fragment ,找不到 ID 的 View

    javascript - 平板电脑中的 Phonegap Android "navigator.notification.activitystart()"

    android - 如何在 Kotlin Android 中正确使用 URL

    reflection - 获得当前类(class)

    android - 无法从 json 下载多个视频文件并将下载路径设置为同一列表

    android - 节能 GPS 跟踪