Android 如何通过触摸中断长时间的处理?
我需要编程一种方式,允许用户通过触摸特定的按钮来中断我的长时间处理,如果用户需要的话 因此,如果他想中断,我需要两个线程,一个运行我的长进程,另一个等待用户交互 从我所读到的所有内容中,我明白了对我来说最好的解决方案是协作 然而,我以前从未在Kotlin中使用过协同程序。所以我在这方面是外行 老实说,互联网上的教程对我来说都不够清晰。 我好不容易才做了一件事 下面的代码位于按钮内部Android 如何通过触摸中断长时间的处理?,android,multithreading,kotlin,Android,Multithreading,Kotlin,我需要编程一种方式,允许用户通过触摸特定的按钮来中断我的长时间处理,如果用户需要的话 因此,如果他想中断,我需要两个线程,一个运行我的长进程,另一个等待用户交互 从我所读到的所有内容中,我明白了对我来说最好的解决方案是协作 然而,我以前从未在Kotlin中使用过协同程序。所以我在这方面是外行 老实说,互联网上的教程对我来说都不够清晰。 我好不容易才做了一件事 下面的代码位于按钮内部btRunOrStop单击处理,它工作正常,与标准有所不同: if (! solved) // Stoppi
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
),而不是使用Kotlin官方文档推荐的isActive
或异常块(try finally
),因为它根本不起作用。这个过程停止了,但只有在它结束之后
我的问题是:
1)在同一活动中使用GlobalScope
是否可能发生内存泄漏
2)如果是,在什么情况下会发生
3)如果是,我如何避免
4)为什么异常处理(
try finally
)或isActive
不起作用 是的,您应该避免使用GlobalScope,因为它会使取消作业和防止泄漏变得笨拙。它是一个单身汉,所以它会比你的活动寿命长。发生泄漏的原因是,launch
lambda正在使用活动的属性捕获对活动的引用
作为旁注,isActive
在您的GlobalScope上始终是正确的,因为您从未取消过您的GlobalScope,只有它的一个子作业。如果一次使用GlobalScope执行多个任务,您可能不想取消它
使用CoroutineScope最简单的方法之一是将其作为委托附加到您的活动中,这样您的活动就是它自己的范围,允许您直接调用launch
。您只需记住在onDestroy()
中取消它,这样作业就不会比您的活动寿命长。(至少不会太久,只要你的工作可以取消——见下文。)
最佳做法是始终让您的挂起
函数在内部为其操作选择正确的调度程序。要使挂起函数可取消,它必须具有退出点,通常是对取消检查挂起函数的调用(如yield()
和delay()
以及您自己调用它们的函数)。您也可以选择检查isActive
,如果其为false,则提前返回,这使得您的函数可以取消,但可以像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
做什么。您只需要像处理流时一样使用它们。您可以将yield()
放在try
或use
块中,并确信最终将执行块,而不考虑取消
为了使作业可取消,我建议在不可运行时使用空变量:
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/*或IO*/){}
定义挂起函数的做法,您可以安全地在其他任何地方执行顺序UI操作,并且启动
块中的代码将非常干净。我将尝试按照您明智的建议执行。但是,我的应用程序是一个单一的活动应用程序。我使用自定义对话框以编程方式创建了所有屏幕和视图,没有XML。即使如此,你相信内存泄漏吗?不知道XML和它有什么关系。如果您的活动已被破坏,但某个寿命较长的对象仍在引用它,则它将泄漏。即使您只有一个活动应用程序,当您退出活动时,应用程序本身也不会立即被杀死,因此如果活动泄漏,您的死应用程序仍将占用内存。如果用户随后再次打开你的应用程序,则现在有两个活动,即使其中一个已死亡。如果这种情况反复发生,您将有几个并发的泄漏活动。这是否真的是一个问题取决于您。也许即使你的工作没有被取消,你仍然确信它会在一秒钟内完成。然后,您泄漏的活动将在一秒钟后被清除,因此暂时泄漏可能是可以的。不过,你肯定不想在网络运营中这样做,因为他们的生活是不可预测的。但是GlobalScope的唯一优点是为您节省了几行代码来创建本地活动范围。为什么仅仅因为你可以,就在这个简单的项目上进行不好的实践呢?当然,我同意你的看法。只是好奇而已,我已经测试过你的建议了<在destroy
中,code>CoroutineScope by MainScope()
和cancel()
起作用,但isActive()
或MainScope()。isActive()
继续返回trueYield
只能在挂起函数中使用,但我在处理过程中使用嵌套函数。当我将所有函数都作为挂起函数时,编译器会给出一个内部错误。
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.
}
}
}