Kotlin 协程会阻塞 Android 中的主线程

问题描述 投票:0回答:2

我是 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
    }  

这是我的视图模型:

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?
    }

这是我的api接口:

interface WebApiService {

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

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

android kotlin retrofit
2个回答
12
投票

您几乎*永远不要在 Android 应用程序中使用

runBlocking
。它仅适用于 JVM 应用程序的
main
函数或测试中,以允许使用在应用程序退出之前完成的协程。否则它就违背了协程的目的,因为它会阻塞直到所有 lambda 返回。

您也不应该使用 GlobalScope,因为它会使 Activity 关闭时取消作业变得很棘手,并且它会在后台线程而不是主线程中启动协程。您应该为活动使用本地范围。您可以通过在活动 (

val scope = MainScope()
) 中创建属性并在
onDestroy()
(
scope.cancel()
) 中取消它来完成此操作。或者,如果您使用
androidx.lifecycle:lifecycle-runtime-ktx
库,则可以仅使用现有的
lifecycleScope
属性。

如果你总是在返回之前

await
你的异步作业,那么你的整个函数将阻塞,直到你得到结果,所以你已经采取了一个后台任务并让它阻塞主线程。

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

  1. 使 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
}
  1. 通过正确的视图模型封装,活动实际上不必启动这样的协程。
    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
}

*如果您正在使用不支持协程的代码库,并且它要求您重写或实现一个非挂起函数,该函数将从主线程调用并可以处理阻塞、长时间运行的代码,那么它使用

runBlocking
作为这两个世界之间的桥梁是可以接受的,以便能够在与该库交互时使用基于协程的代码。


0
投票

我知道互联网/wifi 获取或发布需要处于某种后台/异步任务中。 您是否尝试过使用 Android 注释中的 @Background/@Uithread? 它将要求您将一些依赖项放入 gradle 中。 但这是我处理服务的一种方式。

这是其原始 DOC 的原始链接 https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background

© www.soinside.com 2019 - 2024. All rights reserved.