Kotlin 协程阻塞 Android 中的主线程

2024-05-11

我是 Kotlin 和协程的新手。我有一个fun在我的活动及其内部,检查User用户名和密码,如果为真,则返回Users object.
一切都好。但是当我按下按钮时,我的活动被阻止并等待响应Users login.
我用这个有趣的:

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

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


你几乎*不应该使用runBlocking在 Android 应用程序中。它仅适用于mainJVM 应用程序的功能或在测试中允许使用在应用程序退出之前完成的协程。否则它就违背了协程的目的,因为它会阻塞直到所有 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作为这两个世界之间的桥梁,以便能够在与该库交互时使用基于协程的代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin 协程阻塞 Android 中的主线程 的相关文章

随机推荐

  • 有 Google Keep API 吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 Google Keep 有 API 吗 我想为 Google Keep 制作一个 Windows 8 应
  • 自动检测log4j静态初始化错误的方法

    请注意 这更像是 Bash 问题 而不是 Java 问题 请参阅下面的注释 在每个类中配置log4j时 我们执行以下操作 public class Example private static final Logger log Logger
  • 将 Foq 与 F# 函数类型结合使用

    例如 我使用 F 类型定义来防止函数之间的硬依赖 type IType1 int gt int type IType2 int gt string let func1 i int int i i let func2 i int string
  • 无法使用 jQuery 添加两个小数

    我试图将两个小数值相加 但返回的总和是纯整数 怎么了 我找不到它 欢迎任何帮助 jQuery delivery method ship select change function var cost jQuery this val jQue
  • ObjC 中的 self 是什么?我应该什么时候使用它?

    什么是self在 Objective C 中是什么意思 我应该何时何地使用它 是否类似于this在Java中 self指的是您正在使用的当前类的实例 是的 它类似于this在爪哇 如果您想对该类的当前实例执行操作 则可以使用它 例如 如果您
  • 如何禁用向左滚动?

    I got a div 元素 parent 包含多个子元素 item 我想启用滚动父元素一个方向 left OR正确的 否则什么都不会发生 看我的代码 parent scroll function gt gt gt scroll event
  • 使用 iconv 将 UTF-16BE 转换为无 BOM 的 UTF-8

    我正在尝试使用 iconv 将 UTF 16BE 编码文件 字节顺序标记 0xFE 0xFF 转换为 UTF 8 如下所示 iconv f UTF 16BE t UTF 8 myfile txt 然而 生成的输出具有 UTF 8 字节顺序标
  • Git 子模块在 Windows 上更新缓慢

    Git 子模块在 Windows 上似乎非常慢 为了测试性能 我创建了 3 个裸存储库并向它们提交了 3 条独立消息 未存储文件 然后 我将每个裸存储库作为子模块添加到新的 git 存储库中 并执行子模块更新 花费了 5 秒多的时间 当使用
  • 如何在生产中安全地更改会话 cookie 域或名称?

    我们最近意识到我们的会话 cookie 正在被写入我们网站的完全限定域名 www myapp com 例如 MYAPPCOOKIE 79D5DB83 domain www myapp com 我们希望将其切换为可以跨子域共享的cookie
  • 从Oracle表中删除重复行

    我正在 Oracle 中测试某些内容并使用一些示例数据填充表 但在此过程中我不小心加载了重复记录 因此现在我无法使用某些列创建主键 如何删除所有重复行并只保留其中一行 Use the rowid伪列 DELETE FROM your tab
  • List、IList、IEnumerable、IQueryable、ICollection,哪个返回类型最灵活?

    我之前已经在这里看到过这个问题 但我不满意我理解的完整后果 问题是使用 linq to sql 返回的数据层应该使用什么返回类型以获得最大的灵活性和查询能力 这是我读过 发现的 IEnumerable 是有限的 只允许向前读操作 IEnum
  • 更新 Azure Blob 上的 LastModified

    我正在移植代码以使用 C 中的 Azure 存储 SDK 传统上 我称其为更新修改文件的上次写入 修改时间 File SetLastWriteTimeUtc fileName lastWriteTimeUtc 要更新 blob 的上次修改时
  • 如何创建记录而不将其保存在数据库中

    我正在使用InventoryOdoo 12 的插件 但我的问题可能发生在任何模块上 在这个插件中 一个StockMove模型有一个move line ids field In the Detailed Operations对话框中 我们可以
  • Mojo 配置的自定义类型转换器?

    我需要使用自定义类型 例如LunarDate 在我的 Mojo 对象中 class MyMojo extends AbstractMojo parameter LunarDate lunarDate 我想配置参数
  • 内核与系统中的 Windows 进程

    我有一些与内核和用户模式下的 Windows 进程相关的问题 如果我有一个 hello world 应用程序和一个公开新系统调用 foo 的 hello world 驱动程序 我很好奇一旦处于内核模式 我能做什么和不能做什么 对于初学者来说
  • Sourcetree 2.1.2.5 - 显示“未提交的更改”,但没有任何待处理的内容

    我有一个以前没有遇到过的问题 即使我没有什么可提交的 并尝试将我的分支重置为 Sourcetree 显示的最新提交Uncommitted changes 根据 Atlassian 论坛的说法 通常有两个原因 您的工作目录中有很多很多未暂存的
  • 在 MySQL 表中存储用户密码的最佳 PHP 哈希方法?

    我已经阅读 Stack Overflow 问题大约 15 分钟了 每一个问题似乎都与我之前读到的问题相矛盾 Bcrypt SHA1 MD5 等 我目前对我的密码进行 MD5 但我想让我的数据库在发生泄露时更加安全 我知道这个问题已经被问了一
  • 如何使用 PHP 以任意顺序进行字符搜索(12 个字母,其中 6 个字母构成一个单词)?

    我整天都在想这个问题 似乎无法找出一种记忆有效且快速的方法 问题是 例如 我有这些信 e f j l n rr t t u w x 12 个字母 我正在找这个词 海龟 6 个字母 如何使用 php 找到完整范围 12 个单词 中所有可能的单
  • 当列的数据类型为 int 时,如何用字符串替换 null

    我有一个包含 3 列的表和如下示例数据 所有列都是数据类型int 我有这个查询 select foodid dayid from Schedule 我要更换dayid用字符串 ifdayid null 为此我尝试了这个查询 select f
  • Kotlin 协程阻塞 Android 中的主线程

    我是 Kotlin 和协程的新手 我有一个fun在我的活动及其内部 检查User用户名和密码 如果为真 则返回Users object 一切都好 但是当我按下按钮时 我的活动被阻止并等待响应Users login 我用这个有趣的 priva