首页 > 解决方案 > 为什么协程在使用 setOnTouchListener 时会破坏我的应用程序?

问题描述

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

    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 时会发生这种情况。怎么了?我该怎么办?

标签: androidandroid-studiokotlinkotlin-coroutines

解决方案


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

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

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

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")
    }
}

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


推荐阅读