Coroutine unregister reciever on cancel

被刻印的时光 ゝ 提交于 2019-12-11 07:49:49

问题


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?

The Service works like this:

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

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

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 ...

回答1:


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


来源:https://stackoverflow.com/questions/53517497/coroutine-unregister-reciever-on-cancel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!