首页 > 解决方案 > 如何通过触摸中断长时间的处理?

问题描述

如果用户需要,我需要编程一种方式,允许用户通过触摸特定按钮来中断我的长时间处理。

因此,如果他想中断,我需要 2 个线程,一个运行我的长进程,另一个等待用户交互

从我阅读的所有内容中,我了解到对我来说最好的解决方案是协程

但是,我之前从未在Kotlin中使用过协程。所以我是那个功能的外行。

老实说,互联网上的教程对我来说都不够清楚。困难重重,我能够做点什么。

下面的代码在按钮btRunOrStop点击处理内部,它工作正常,与标准有所不同:

  if (! solved)   // Stopping the long process
    bStat.setText("")  // clearing the progress 
    solving = false
    runBlocking { jobGl.cancelAndJoin()} // Cancel and ends my long process.
  } else {    // Starting the long process
    bStat.setText("Solving ...") // static progress 
    GlobalScope.launch {          
      try {
      solving = true   // Warn that the long process is running
      jobGl  = async {
        runningProcess(param)   // Finally my rocket was launched
      } 
      retGl = jobGl.await()  // return of my process, after running OK
      solved = true          // warn that the process has ended OK.
      solving = false        // No more pending process
  // Generate an artificial button touch
      mEv = MotionEvent.obtain(x,y,MotionEvent.ACTION_UP,0F,0F,0)
  // False click to post processing after process OK.
      btRunOrStop.dispatchTouchEvent(mEv)  
     }
     finally{}  // Exception handing. It does not  work.
    }
   }
   else { code after successful execution}

最后是我漫长的过程:

suspend fun solveEquac(param:Double):Double {
  ..... if (! solving) return ......
  ..... if (! GlobalScope.isActive) return ...  // It does not work.
}

不幸的是,我被要求使用变量 ( solving) 而不是使用isActive或异常块 ( ),这是Kotlintry finally官方文档推荐的,因为它不起作用。该过程停止,但只有在它结束之后。

我的问题:

1)GlobalScope在同一活动中 使用是否可能导致内存泄漏?

2)如果是,它会在什么情况下发生?

3)如果是,我该如何避免?

4)为什么异常处理(try finally)或isActive不起作用?

标签: androidmultithreadingkotlin

解决方案


是的,您应该避免使用 GlobalScope,因为它会使取消作业和防止泄漏变得笨拙。它是一个单例,所以它会比你的活动更长寿。发生泄漏是因为您的launchlambda 通过使用 Activity 的属性来捕获对您的 Activity 的引用。

作为旁注,isActive您的 GlobalScope 始终如此,因为您从未取消您的 GlobalScope,只有它的一个子作业。如果您一次将 GlobalScope 用于一项以上的工作,您可能不想取消它。

使用 CoroutineScope 最简单的方法之一就是将它作为委托附加到您的 Activity,因此您的 Activity 是它自己的范围,允许您直接调用launch. 你只需要记住取消它,onDestroy()这样工作就不会超过你的活动。(至少不会长久,只要你让你的工作可以取消——见下文。)

class MyActivity: AppCompatActivity(), CoroutineScope by MainScope() {
    //...
    override fun onDestroy() {
        super.onDestroy()
        cancel() // cancel any jobs in this Activity's scope
    }
}

最佳实践是始终让您的suspend函数在内部为其操作选择正确的调度程序。要使您的挂起函数可取消,它必须是其中包含退出点的东西,这些退出点通常是调用取消检查挂起函数(如调用它们的yield()和和您自己的函数)。delay()如果它为假,您也可以检查isActive并提前返回,这使您的函数可取消但可用,就像yield()退出另一个挂起函数一样。所以如果有一个大的for循环,你可以yield()在循环中调用给它一个退出点。或者,如果有一堆连续的步骤,则yield()在其间放置调用。另一种方法是继续检查状态isActive,这是上下文的属性,因此您可以直接从withContext块中引用它。

// Just an example. You'll have to come up with a way to break up your calculation.
suspend fun solveEquation(param:Double): Double = withContext(Dispatchers.Default) {
    val x = /** some calculation */
    yield()
    val y = /** some calculation with x */
    yield()
    var z = /** some calculation with y */
    for (i in 0..1000) {
        yield()
        z += /** some calculation */
    }
    z
}

我不确定你想做什么try/finally。您只需要在处理流时像往常一样使用它们。您可以将 ayield()放在tryoruse块内,并确信该finally块将执行而不管取消。

为了使您的工作可以取消,我建议使用一个在不可运行时为 null 的变量:

var calcJob: Job? = null // Accessed only from main thread.

然后你的按钮监听器可以是这样的:

btRunOrStop.onClickListener = {
    // Cancel job if it's running. 
    // Don't want to join it because that would block the main thread.
    calcJob?.let {
        bStat.setText("")
        it.cancel()
        calcJob = null
    } ?: run { // Create job if one didn't exist to cancel
        calcJob = launch { // Called on the Activity scope so we're in the main UI thread.
            bStat.setText("Solving ...")
            mEv = MotionEvent.obtain(x,y,MotionEvent.ACTION_UP,0F,0F,0)
            btRunOrStop.dispatchTouchEvent(mEv)
            // You might want to check this doesn't trigger the onClickListener and
            // create endless loop. (I don't know if it does.)

            val result = solveEquation(someParam) // background calculation
            // Coroutine resumes on main thread so we can freely work with "result"
            // and update UI here.

            calcJob = null // Mark job as finished.
        }
    }
}

因此,通过遵循使用= withContext(Dispatchers.Default /*or IO*/){}来定义挂起函数的做法,您可以安全地在其他任何地方执行顺序 UI 操作,并且代码launch块中的代码将非常干净。


推荐阅读