【问题标题】:Why does the coroutine break my app when using setOnTouchListener?为什么协程在使用 setOnTouchListener 时会破坏我的应用程序?
【发布时间】:2021-08-16 14:01:49
【问题描述】:

我想在用户触摸按钮时调用一些代码。当用户停止按下时,应用程序应该停止向服务器发送信息 - 音频块。我想我应该使用协程。但屏幕冻结,应用中断。

    override fun onTouch(v: View?, event: MotionEvent?): Boolean {

                runBlocking {

                    var job : Job? = null

                    when (event?.action) {
                        MotionEvent.ACTION_DOWN -> {
                            val startTime = System.currentTimeMillis()
                            val job = launch(Dispatchers.Default) {
                                var nextPrintTime = startTime
                                var i = 0
                                while (isActive) { // cancellable computation loop
                                    // print a message twice a second
                                    if (System.currentTimeMillis() >= nextPrintTime) {
                                        println("job: I'm sleeping ${i++} ...")
                                        nextPrintTime += 500L
                                    }
                                }
                            }

                        }
                        MotionEvent.ACTION_UP -> {
                            println("main: I'm tired of waiting!")
                            job?.cancelAndJoin() // cancels the job and waits for its completion
                            println("main: Now I can quit.")
                        }
                        else ->
                            Log.i("else", "else")
                    }

                }


                return v?.onTouchEvent(event) ?: true
            }
        })

我想,当使用带有 MotionEvent 的 onTouch 时会发生这种情况。怎么了?我该怎么办?

【问题讨论】:

    标签: android android-studio kotlin kotlin-coroutines


    【解决方案1】:

    切勿在应用中使用runBlocking。它用于测试或用于没有 GUI 的本机 Java 控制台应用程序中的 main 函数。

    runBlocking 违背了使用协程的目的并阻塞了调用它的线程,导致您的异步工作阻塞了线程。在这种情况下,由于它是从主线程调用的,它会阻塞主线程,因此您的 UI 将冻结,并且永远无法接收到 ACTION_UP 信号。

    要修复您当前的代码,您应该从 lifecycleScope 启动协程,而不是使用 runBlocking。此外,您在 ACTION_DOWN 部分中隐藏了 job 变量。您需要使用现有变量才能在该范围之外取消它。

    override fun onTouch(v: View, event: MotionEvent): Boolean {
        var job : Job? = null
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                val startTime = System.currentTimeMillis()
                job = lifecycleScope.launch {
                    var nextPrintTime = startTime
                    var i = 0
                    while (true) { // cancellable computation loop
                        yield()
                        // print a message twice a second
                        if (System.currentTimeMillis() >= nextPrintTime) {
                            println("job: I'm sleeping ${i++} ...")
                            nextPrintTime += 500L
                        }
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
                lifecycleScope.launch {
                    println("main: I'm tired of waiting!")
                    job?.cancelAndJoin() // cancels the job and waits for its completion
                    println("main: Now I can quit.")
                }
            }
            else -> Log.i("else", "else")
        }
    }
    

    但是,您的用例似乎只是从该侦听器开始或停止某些事情。我不确定为此使用协程是否有意义。也许您处理上传工作的类会在内部使用协程,但不一定必须使用挂起函数来启动或停止。

    【讨论】:

    • 我不认为这有帮助,代码仍然没有挂起并且有一个忙等待循环。我认为循环中的yield() 会有所帮助。当然,正如您所注意到的,它一开始就不应该有循环。
    • @MarkoTopolnik 不错。谢谢。我编辑它以使用产量。我不认为我会这样做,因为它仍然非常忙于将 Runnables 发送到主循环器,但我不认为这是 OP 的预期任务,因为它是从文档示例中复制的。如果可以随时间漂移而不是恰好每秒发生两次,则重复动作可能会使用延迟。
    • 我认为它不会导致饥饿,因为处理程序必须出列才能使另一个事件入队,该事件进入队列的后面。与此同时,任何其他事件都可以排队等待处理。但无论如何,这都是一个糟糕的解决方案。 GUI 应用程序允许非常精确的时间以支持流畅的动画/过渡,您可以在每个屏幕帧上触发一个事件。
    猜你喜欢
    • 2011-05-06
    • 1970-01-01
    • 2021-02-04
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    • 2016-11-25
    • 1970-01-01
    • 2012-05-19
    相关资源
    最近更新 更多