Android 取消SuspendCancelableCorroutine内的回调

Android 取消SuspendCancelableCorroutine内的回调,android,callback,kotlin-coroutines,cancellation,Android,Callback,Kotlin Coroutines,Cancellation,我在SuspendCancelableCorroutine中包装了一个回调,以将其转换为一个挂起函数: suspend fun TextToSpeech.speakAndWait(text: String) : Boolean { val uniqueUtteranceId = getUniqueUtteranceId(text) speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId) return s

我在SuspendCancelableCorroutine中包装了一个回调,以将其转换为一个挂起函数:

suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
    val uniqueUtteranceId = getUniqueUtteranceId(text)
    speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
    return suspendCancellableCoroutine { continuation ->
        this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
            override fun onDone(utteranceId: String?) {
                if(utteranceId == uniqueUtteranceId) {
                    Timber.d("word is read, resuming with the next word")
                    continuation.resume(true)
                }
            }
        })
    }
}
我使用片段的lifecycleScope协同程序作用域调用这个函数,我假设当片段被销毁时它被取消。但是,LeakCanary报告说,由于这个侦听器,我的片段正在泄漏,并且我用日志验证了回调是否在取消协同路由后被调用

因此,用SuspendCancelableCorroutine而不是SuspendCorroutine包装似乎不足以取消回调。我想我应该主动检查工作是否活跃,但如何检查?我尝试了
coroutineContext.ensurereactive()
并在回调中检查
coroutineContext.isActive
,但IDE给出了一个错误,即“只能在coroutine主体内调用挂起函数”。如果取消作业,我还能做些什么来确保它不会恢复

LeakCanary报告说,由于这个侦听器,我的片段正在泄漏,并且我用日志验证了回调是否在取消协同路由后被调用

是的,底层异步API不知道Kotlin协同路由,您必须使用它来显式传播取消。Kotlin专门为此提供了
invokeOnCancellation
回调:

return suspendCancellableCoroutine { continuation ->
    this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() { 
        /* continuation.resume() */ 
    })
    continuation.invokeOnCancellation {
        this.setOnUtteranceProgressListener(null)
    }
}
LeakCanary报告说,由于这个侦听器,我的片段正在泄漏,并且我用日志验证了回调是否在取消协同路由后被调用

是的,底层异步API不知道Kotlin协同路由,您必须使用它来显式传播取消。Kotlin专门为此提供了
invokeOnCancellation
回调:

return suspendCancellableCoroutine { continuation ->
    this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() { 
        /* continuation.resume() */ 
    })
    continuation.invokeOnCancellation {
        this.setOnUtteranceProgressListener(null)
    }
}

除了公认的答案之外,我还认识到continuation对象还有一个isActive属性。因此,我们也可以在恢复之前检查回调中的协同路由是否仍然处于活动状态:

    return suspendCancellableCoroutine { continuation ->
        this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() 
        {
            override fun onDone(utteranceId: String?) {
                if(utteranceId == uniqueUtteranceId) {
                    if (continuation.isActive) {
                        continuation.resume(true)
                    }
                }
            }
        })
        continuation.invokeOnCancellation {
            this.setOnUtteranceProgressListener(null)
        }
    }

除了公认的答案之外,我还认识到continuation对象还有一个isActive属性。因此,我们也可以在恢复之前检查回调中的协同路由是否仍然处于活动状态:

    return suspendCancellableCoroutine { continuation ->
        this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() 
        {
            override fun onDone(utteranceId: String?) {
                if(utteranceId == uniqueUtteranceId) {
                    if (continuation.isActive) {
                        continuation.resume(true)
                    }
                }
            }
        })
        continuation.invokeOnCancellation {
            this.setOnUtteranceProgressListener(null)
        }
    }

如果您想删除您的
jelisutranceprogresslistener
,而不考虑结果(成功、取消或其他错误),您可以使用经典的try/finally块:

suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
    val uniqueUtteranceId = getUniqueUtteranceId(text)
    speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
    return try { 
        suspendCancellableCoroutine { continuation ->
            this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
                override fun onDone(utteranceId: String?) {
                    if(utteranceId == uniqueUtteranceId) {
                        Timber.d("word is read, resuming with the next word")
                        continuation.resume(true)
                    }
                }
        })
    } finally {
        this.setOnUtteranceProgressListener(null)
    }
}

如果您想删除您的
jelisutranceprogresslistener
,而不考虑结果(成功、取消或其他错误),您可以使用经典的try/finally块:

suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
    val uniqueUtteranceId = getUniqueUtteranceId(text)
    speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
    return try { 
        suspendCancellableCoroutine { continuation ->
            this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
                override fun onDone(utteranceId: String?) {
                    if(utteranceId == uniqueUtteranceId) {
                        Timber.d("word is read, resuming with the next word")
                        continuation.resume(true)
                    }
                }
        })
    } finally {
        this.setOnUtteranceProgressListener(null)
    }
}

您有
continuation.invokeOnCancellation{callback.cancel()}
谢谢,这很有帮助!我的回调没有cancel方法,但我尝试在取消时删除侦听器,它成功了:
continuation.invokeonConcellation{this.setonutternanceProgressListener(null)}
不再泄漏!再次感谢!您想将其添加为答案吗?您有
continuation.invokeOnCancellation{callback.cancel()}
谢谢,这很有帮助!我的回调没有cancel方法,但我尝试在取消时删除侦听器,它成功了:
continuation.invokeonConcellation{this.setonutternanceProgressListener(null)}
不再泄漏!再次感谢!您想添加它作为答案吗?您是对的,最好还是删除侦听器。但问题的主要问题是立即取消作业并防止调用片段泄漏。但我可以将此与其他建议的解决方案结合起来。谢谢@OyaCanlı问题是,由于您的继续操作处于挂起状态,它将立即通过(重新)抛出
CancellationException
并输入
finally
block来完成。在这种情况下,您不需要显式取消侦听器。啊哈!我明白了,你是对的!这同样有效。谢谢澄清!你说得对,最好还是删除侦听器。但问题的主要问题是立即取消作业并防止调用片段泄漏。但我可以将此与其他建议的解决方案结合起来。谢谢@OyaCanlı问题是,由于您的继续操作处于挂起状态,它将立即通过(重新)抛出
CancellationException
并输入
finally
block来完成。在这种情况下,您不需要显式取消侦听器。啊哈!我明白了,你是对的!这同样有效。谢谢澄清!