Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Coroutine doesn't stop when the service containing it stops

I have a foreground service that changes wallpapers with a delay, so I'm using coroutines inside of it,I call it from another class with startService(Intent(this,MySerivce::class.java)) but when I stop it with stopService(Intent(this,MySerivce::class.java)), only functions on main thread stop (like showNotification()), here's my code:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val externalStorageState = Environment.getExternalStorageState()
    if (externalStorageState.equals(Environment.MEDIA_MOUNTED)){


    val root=getExternalFilesDir(null)?.absolutePath
        val myDir = File("$root/saved_images")
        val list = myDir.listFiles()
        if (myDir.exists()&&list.size>=2){
            CoroutineScope(Dispatchers.IO).launch {
                val time = intent?.getIntExtra("TIME",0)
                if (time != null) {
                    manageWallpapers(list,time)
                }

            }
        }}

    
    showNotification()
    
    return START_STICKY
}
}

and here's manageWallpapers function

private suspend fun manageWallpapers(list: Array<File>?, time:Int){

    while (true){
        if (list != null) {
            for (image in list){

                setWallpaper(image)
                delay(time.toLong())
                
            }
        }

    }

    
}
like image 495
khaled baccour Avatar asked Oct 26 '25 09:10

khaled baccour


2 Answers

You need to keep a reference to the job returned by launch and call cancel() on it when your service is stopped.

private var job: Job? = null

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val externalStorageState = Environment.getExternalStorageState()
    if (externalStorageState.equals(Environment.MEDIA_MOUNTED)){


    val root=getExternalFilesDir(null)?.absolutePath
        val myDir = File("$root/saved_images")
        val list = myDir.listFiles()
        if (myDir.exists()&&list.size>=2){
            job = CoroutineScope(Dispatchers.IO).launch {   // HERE
                val time = intent?.getIntExtra("TIME",0)
                if (time != null) {
                    manageWallpapers(list,time)
                }

            }
        }}

    
    showNotification()
    
    return START_STICKY
}
}

override fun onDestroy() {
    job?.cancel()
    job = null
    super.onDestroy()
}

Another option is to keep a reference to your CoroutineScope and call cancel on it.

like image 189
Francesc Avatar answered Oct 27 '25 23:10

Francesc


If you subclass from LifecycleService, you can use lifecycleScope to launch your coroutine, so it will be cancelled automatically when the service stops.

build.gradle:

implementation 'androidx.lifecycle:lifecycle-service:2.3.1'

In your service, a subclass of LifecycleService:

lifecycleScope.launch {
    val time = intent?.getIntExtra("TIME",0)
    if (time != null) {
        manageWallpapers(list,time)
    }
}

There's no need to specify Dispatchers.IO, since you are only calling a suspend function, not a blocking function.

like image 32
Tenfour04 Avatar answered Oct 27 '25 23:10

Tenfour04