← Back to Tutorials Chapter 10

Services & Background Tasks

Started Services

A Started Service runs in the background indefinitely, even if the activity that started it is destroyed.

class DownloadService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val url = intent?.getStringExtra("URL") ?: return START_NOT_STICKY

        thread {
            downloadFile(url)
            stopSelf()
        }

        return START_STICKY // Restart if killed
    }

    private fun downloadFile(url: String) {
        // Download logic
        sendBroadcast(Intent("DOWNLOAD_COMPLETE"))
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

// Start service
startService(Intent(this, DownloadService::class.java).apply {
    putExtra("URL", "https://example.com/file.zip")
})

Bound Services

class MusicService : Service() {
    private val binder = LocalBinder()
    private var mediaPlayer: MediaPlayer? = null

    inner class LocalBinder : Binder() {
        fun getService(): MusicService = this@MusicService
    }

    override fun onBind(intent: Intent?): IBinder = binder

    fun playMusic(song: Int) {
        mediaPlayer = MediaPlayer.create(this, song)
        mediaPlayer?.start()
    }

    fun stopMusic() {
        mediaPlayer?.stop()
        mediaPlayer?.release()
        mediaPlayer = null
    }
}

// Activity binding
class MainActivity : AppCompatActivity() {
    private var musicService: MusicService? = null
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            musicService = (service as MusicService.LocalBinder).getService()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            musicService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        bindService(Intent(this, MusicService::class.java), connection, Context.BIND_AUTO_CREATE)
    }
}

Foreground Services

class ForegroundLocationService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = NotificationCompat.Builder(this, "location")
            .setContentTitle("Location Tracking")
            .setContentText("Tracking your location in background")
            .setSmallIcon(R.drawable.ic_location)
            .setOngoing(true)
            .build()

        startForeground(1002, notification)
        // Start location updates
        return START_STICKY
    }
}

// AndroidManifest.xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service android:name=".ForegroundLocationService"
    android:foregroundServiceType="location" />

WorkManager

WorkManager is the recommended solution for deferrable background work that needs guaranteed execution.

// Define worker
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            // Sync data to server
            syncData()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

// Schedule one-time work
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .setRequiresBatteryNotLow(true)
        .build())
    .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(this).enqueue(syncRequest)

// Observe work status
WorkManager.getInstance(this).getWorkInfoByIdLiveData(syncRequest.id)
    .observe(this) { info ->
        when (info?.state) {
            WorkInfo.State.SUCCEEDED -> println("Sync successful")
            WorkInfo.State.FAILED -> println("Sync failed")
        }
    }

// Periodic work
val periodicSync = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
    .build()
WorkManager.getInstance(this).enqueue(periodicSync)

JobScheduler

val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler

val job = JobInfo.Builder(123, ComponentName(this, MyJobService::class.java))
    .setMinimumLatency(1000) // 1 second delay
    .setOverrideDeadline(5000) // Must run within 5 seconds
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setRequiresCharging(false)
    .build()

jobScheduler.schedule(job)
Exercise: Create a podcast app that uses a Foreground Service to play audio even when the app is in the background. Show a persistent notification with play/pause and stop controls. Use WorkManager to periodically check for new episodes every hour when the device is connected to Wi-Fi.