协程在注销时注销接收者 [英] Coroutine unregister reciever on cancel

查看:120
本文介绍了协程在注销时注销接收者的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

服务停止时,我的协程泄漏了广播接收器.这是因为服务在回调完成之前已停止.如何取消协程以让我取消注册接收者的方式?

My coroutine leaks a broadcast receiver, when the service is stopped. This is because the service stops, before the callback is finished. How can I cancel coroutine in a way that lets me unregister the reciever?

Service的工作方式如下:

class DataCollectorService : Service(){
    var job : Job? = null

    override fun onStartCommand(...){
        job = GlobalScope.launch {
            val location = async { wifiScanner.getCurrentLocation() }
            //other asynchronous jobs
            location.await() 
            //do something with location
        }
    }

    override fun onDestroy(){
         job?.cancel()
    }
}

这是在onDestroy中未正确注销BroadcastReciever的类:

Here is the class where the BroadcastReciever is not unregistered properly in onDestroy:

class WifiScanner(val context: ContextWrapper) {
    val wifiManager: WifiManager

    init {
        wifiManager = context.baseContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
    }

    suspend fun getCurrentScanResult(): List<ScanResult> =
        suspendCoroutine { cont ->
            val wifiScanReceiver = object : BroadcastReceiver() {
                override fun onReceive(c: Context, intent: Intent) {
                    if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
                        context.unregisterReceiver(this)
                        cont.resume(wifiManager.scanResults)
                    }
                }
            }
            context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
            wifiManager.startScan()
        }
}

stacktrace示例:

Example stacktrace:

de.leo.smartTrigger E/ActivityThread: Service de.leo.smartTrigger.datacollector.datacollection.DataCollectorService has leaked IntentReceiver de.leo.smartTrigger.datacollector.datacollection.sensors.WifiScanner$getCurrentScanResult$$inlined$suspendCoroutine$lambda$1@6febc2f that was originally registered here. Are you missing a call to unregisterReceiver()?
        android.app.IntentReceiverLeaked: Service de.leo.smartTrigger.datacollector.datacollection.DataCollectorService has leaked IntentReceiver de.leo.smartTrigger.datacollector.datacollection.sensors.WifiScanner$getCurrentScanResult$$inlined$suspendCoroutine$lambda$1@6febc2f that was originally registered here. Are you missing a call to unregisterReceiver()?
            at android.app.LoadedApk$ReceiverDispatcher.<init>(LoadedApk.java:1355)
            at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:1120)
            at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1428)
            at android.app.ContextImpl.registerReceiver(ContextImpl.java:1401)
            at android.app.ContextImpl.registerReceiver(ContextImpl.java:1389)
            at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:622)
            at de.leo.smartTrigger.datacollector.datacollection.sensors.WifiScanner.getCurrentScanResult(WifiScanner.kt:35)
            at de.leo.smartTrigger.datacollector.datacollection.DataCollectorService.getWifi(DataCollectorService.kt:219)
            at de.leo.smartTrigger.datacollector.datacollection.DataCollectorService$uploadDataSet$1$wifi$1.invokeSuspend(DataCollectorService.kt:273)
            at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
            at kotlinx.coroutines.DispatchedTask$DefaultImpls.run(Dispatched.kt:221)
            at kotlinx.coroutines.DispatchedContinuation.run(Dispatched.kt:67)
            at ...

推荐答案

答案确实是使用suspendCancellableCoroutine并定义cont.invokeOnCancellation,如下所示:

The answer was indeed to use suspendCancellableCoroutine and define cont.invokeOnCancellation as written below:

suspend fun getCurrentScanResult(): List<ScanResult> =
        suspendCancellableCoroutine { cont ->
            //define broadcast reciever
            val wifiScanReceiver = object : BroadcastReceiver() {
                override fun onReceive(c: Context, intent: Intent) {
                    if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
                        context.unregisterReceiver(this)
                        cont.resume(wifiManager.scanResults)
                    }
                }
            }
            //setup cancellation action on the continuation
            cont.invokeOnCancellation {
                context.unregisterReceiver(wifiScanReceiver)
            }
            //register broadcast reciever
            context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
            //kick off scanning to eventually receive the broadcast
            wifiManager.startScan()
        }

这篇关于协程在注销时注销接收者的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆