π‘ Android Fragment Lifecycle Guide
βοΈ Android Service Lifecycle: Deep Dive with Kotlin & Best Practices
Android Service
is a powerful component for performing long-running operations in the background β like playing music, handling downloads, or syncing data β without a user interface.
Understanding the lifecycle of a Service is crucial to building robust and battery-efficient apps. In this post, weβll cover:
- Types of Services
- Lifecycle methods
- Code examples in Kotlin with explanations
- Tips for modern development
π Types of Android Services
Type | Description |
---|---|
Started Service | Launched via startService() or ContextCompat.startForegroundService() . Runs until stopSelf() or stopService() is called. |
Bound Service | Bound to one or more components using bindService() . Stops when all clients unbind. |
Foreground Service | Similar to started service, but shows a notification to run in foreground (avoids background restrictions). |
π Service Lifecycle Diagram (Started Service)
1
2
3
4
5
6
7
8
9
onCreate()
β
onStartCommand()
β
Service Running
β
stopSelf() / stopService()
β
onDestroy()
π Service Lifecycle Diagram (Bound Service)
1
2
3
4
5
6
7
onCreate()
β
onBind()
β (multiple clients)
onUnbind()
β
onDestroy()
π§ͺ Kotlin Code Examples with Explanation
β Basic Started Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyStartedService : Service() {
override fun onCreate() {
super.onCreate()
Log.d("Service", "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("Service", "onStartCommand")
// Do background task here
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Log.d("Service", "onDestroy")
}
override fun onBind(intent: Intent?): IBinder? {
return null // Not a bound service
}
}
Explanation:
onCreate()
: Called when the service is created.onStartCommand()
: Called each timestartService()
is called. Your background work starts here.START_STICKY
: Tells the system to recreate the service after it has been killed.onDestroy()
: Cleanup resources here.onBind()
returns null since itβs not a bound service.
To start this service:
1
2
val intent = Intent(this, MyStartedService::class.java)
startService(intent)
β Bound Service Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyBoundService : Service() {
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
fun getService(): MyBoundService = this@MyBoundService
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
fun getTimestamp(): Long = System.currentTimeMillis()
}
Explanation:
- The service exposes a binder interface to allow activities to call methods like
getTimestamp()
. - When an activity binds,
onBind()
is called and theLocalBinder
is returned.
Binding from activity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private var bound = false
private var service: MyBoundService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val localBinder = binder as MyBoundService.LocalBinder
service = localBinder.getService()
bound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
bound = false
}
}
override fun onStart() {
super.onStart()
Intent(this, MyBoundService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
if (bound) {
unbindService(connection)
bound = false
}
}
β Foreground Service Example (Modern Approach)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, "service_channel")
.setContentTitle("Foreground Service")
.setContentText("Running...")
.setSmallIcon(R.drawable.ic_notification)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
Explanation:
- Required for Android 8.0+ if youβre running a long task in the background.
startForeground()
keeps your service alive with a persistent notification.
Create a notification channel:
1
2
3
4
5
6
7
8
9
10
11
fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"service_channel",
"Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = context.getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
π§ββοΈ Tips & Best Practices
π 1. Prefer WorkManager for Background Work
Use WorkManager
for jobs that need guaranteed execution and system-friendly constraints:
1
2
val workRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(workRequest)
β οΈ 2. Always use Foreground Services for Long Tasks
Long tasks like audio recording, location updates should use foreground services with notifications.
π§Ό 3. Stop Services Explicitly
1
stopService(Intent(this, MyStartedService::class.java))
Or from within:
1
stopSelf()
π 4. Use JobIntentService for Pre-Oreo Compatibility
If supporting API < 26, use JobIntentService
to queue tasks safely.
π§ Conclusion
Android Services give your app powerful background capabilities β but with great power comes responsibility. Choose the right type of service, respect the lifecycle, and always use modern alternatives (like WorkManager) when appropriate.