Introduction
Services is an Android app component that is used to perform long-running task in the background that does not have a UI. It can be used for various functionalities which don't need user input such as playing music, downloading data, watching network traffic and much more. Services can also be used for building a continuous connection to a distant server, for various purposes such as sending or receiving messages from a chat server.
Types of Services in Android
Foreground Services
Background Services
Bound Services
Foreground Services
Foreground services are those services that notify the users about ongoing operations by displaying a notification in the notification bar. Users can interact with the service through the notification provided by the task. These services can keep running even if the app is terminated. Example use cases:
A music streaming app uses a foreground service for continuous playback and player controls.
A video call app employs a foreground service for maintaining an active call session with video and audio.
An audio recording app starts a foreground service for capturing high-quality audio recordings.
Here is a simple example code for a Foreground Service:
Service Class (ForegroundService.kt):
class ForegroundService : Service() {
private val TAG = "ForegroundService"
companion object {
var isServiceRunning = false
}
override fun onCreate() {
super.onCreate()
showToast("Service is being created")
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showToast("Service is starting")
isServiceRunning = true
val notificationManager =
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Create a notification channel
val channel = NotificationChannel(
"notification-channel-id",
"notification-channel-name",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
// Build the notification
val notification =
NotificationCompat.Builder(this, "notification-channel-id")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("Foreground Service Running!")
.setContentTitle("Foreground Service")
.build()
// Start the service in the foreground with the notification
startForeground(1, notification)
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
isServiceRunning = false
showToast("Service is being destroyed")
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Activity Class (ServiceActivity.kt):
class ServiceActivity : AppCompatActivity() {
private lateinit var binding: Activity2serviceBinding
private lateinit var foregroundIntent: Intent
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity2serviceBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create an intent to start ForegroundService
foregroundIntent= Intent(this, ForegroundService::class.java)
binding.startService.setOnClickListener {
// Check if ForegroundService is already running
if (ForegroundService.isServiceRunning) {
Toast.makeText(this, "Service is already running", Toast.LENGTH_SHORT).show()
} else {
// Start the ForegroundService if not already running
startService(foregroundIntent)
}
}
binding.stopService.setOnClickListener {
// Stop the ForegroundService
stopService(foregroundIntent)
}
}
}
Manifest File (AndroidManifest.kt):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<!-- Permission to post notifications. Required for foreground services to show notifications. -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Permission to run foreground services. Required to start services in the foreground. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
<!-- Other application components like activities and receivers -->
...
<!-- Declaration of the ForegroundService. This tells the system about the service so it can be started and used by the app. -->
<service android:name=".ForegroundService"/>
</application>
</manifest>
Background Services
Background Services are those that perform tasks without directly notifying the users or requiring interaction from users. These services work in the background, i.e., they can continue running even when the app is not actively visible on the screen or when the device is locked. However, it's important to note that if the app is stopped or no longer running, background services associated with that app will also stop running, as they are tied to the lifecycle of the app. Example use cases:
A backup app initiates background services for scheduled backups of user data to cloud storage.
A news app uses background services to fetch and update news feeds at set intervals.
An app that syncs data with a server uses background services for periodic synchronization without user intervention.
Here is a simple example code for a Background Service:
Service Class (BackgroundService.kt):
class BackgroundService : Service() {
override fun onCreate() {
super.onCreate()
showToast("Service is being created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showToast("Service is starting")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
showToast("Service is being destroyed")
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Activity Class (ServiceActivity.kt):
class ServiceActivity : AppCompatActivity() {
private lateinit var binding: Activity2serviceBinding
private lateinit var backgroundIntent: Intent
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity2serviceBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create an intent to start BackgroundService
backgroundIntent = Intent(this, BackgroundService::class.java)
binding.startService.setOnClickListener {
// Start the BackgroundService if not already running
startService(backgroundIntent)
}
binding.stopService.setOnClickListener {
// Stop the BackgroundService
stopService(backgroundIntent)
}
}
override fun onDestroy() {
// Stop the BackgroundService (prevent memory leaks)
stopService(backgroundIntent)
super.onDestroy()
}
}
Manifest File (AndroidManifest.kt):
<manifest>
<application>
...
<service android:name=".BackgroundService"/>
</application>
</manifest>
Bound Services
Bound Services allows app components like activities to bind themselves to the service. These services perform the task as long as the component is bound to it. Multiple components can bind themselves with a service at a time. To bind a service to the component bindService()
method is used and components should unbind from the service when they no longer require its functionality to release resources and avoid memory leaks. Example use cases:
A messaging app binds to a service for message delivery and synchronization across devices.
A fitness tracker app binds to a service for real-time location tracking during workouts.
A file transfer app binds to a service for managing file uploads and downloads in the background.
Lifecycle of a Service
In Android, there are two categories of services bound services and started services and each of these has a different lifecycle.
Started Service Lifecycle
This service will start when an app component calls the startService()
method. After that, the service can keep on running even if the component that started the service is destroyed. To stop a running service we can use the stopService()
method or the service can stop itself using the stopSelf()
method.
onCreate(): This method gets called when the service is first created using
startService()
method. It is used to perform one-time setup procedures such as initializing resources that will be used by the service.onStartCommand(): This method gets called every time when the service is started using
startService()
method. If the service is running already, this method will be called again with a new intent. This is where you define what the service should do in response to the intent passed bystartService()
.onDestroy(): The
onDestroy()
method is called when the service is no longer used and is destroyed. The service is destroyed using thestopService()
method which is called by the app component that was used to start the service or the service can stop itself using thestopSelf()
method. This is where we should clean up all the resources that are used in the service like removing listeners, unregistering receivers etc.
You can check the Foreground Service or Background Service for examples.
Bound Service Lifecycle
onCreate(): This method gets called when the service is first created using
startService()
method orbindService()
method.onStartCommand(): [same as in started service]. It won't get called if
bindService()
is used to start the service.onBind(): The
onBind()
method is called when a component binds to the service using thebindService()
method. If the service is not running already thenonCreate()
will be called first thenonBind()
else theonBind()
will be called directly. It returns an IBinder instance that is used by the component to communicate with the service. The IBinder acts as an interface for the component to call method within the service.What binding really means here is that a component (for example, an activity) establishes a connection with the Service class to call its methods for performing some actions or retrieving data.
onUnbind(): The
onUnbind()
method is called when the service is unbound from the component using theunBindServce()
method. This is where we can clean up the resources related to binding that are no longer needed. It returns a boolean value that decides if you want to call theonRebind(
) method, returns true if you want to callonRebind()
else return false.onRebind(): The
onRebind()
method is called only ifonUnbind()
returns true. This method is used to handle new binding requests after the service has been unbound or to reinitialize the resources that have been cleaned up in theonUnbind()
method.onDestroy(): [same as in started service]
Here is a simple example code for a Background Service:
Service Class (BoundService.kt):
class BoundService: Service() {
private val TAG = "BoundService"
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
// Return this instance of MyBoundService so clients can call public methods
fun getService(): BoundService = this@BoundService
}
// Called when the service is first created.
override fun onCreate() {
super.onCreate()
showToast("Service is being created")
}
// Called every time a component explicitly starts the service using startService()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showToast("Service is starting")
return super.onStartCommand(intent, flags, startId)
}
// Called when a component binds to the service using bindService()
override fun onBind(intent: Intent?): IBinder? {
showToast("Service is being bound")
return binder
}
// Called when all components have unbound from the service
override fun onUnbind(intent: Intent?): Boolean {
showToast("Service is being unbound")
return super.onUnbind(intent)
}
// Called when a client re-binds to the service after it had previously unbound
override fun onRebind(intent: Intent?) {
showToast("Service is being re-bound")
super.onRebind(intent)
}
// Called when the service is no longer used and is being destroyed
override fun onDestroy() {
showToast("Service is being destroyed")
super.onDestroy()
}
fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
fun getData(): String {
return "Data from Bound Service"
}
}
Activity Class (ServiceActivity.kt):
class ServiceActivity : AppCompatActivity() {
private lateinit var binding: Activity2serviceBinding
private lateinit var boundIntent: Intent
// Reference to the bound service
private var boundService: BoundService? = null
// Flag to track if the service is bound
private var isBound: Boolean = false
// ServiceConnection to manage connection with the BoundService
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as BoundService.LocalBinder
boundService = binder.getService()
isBound = true
Log.d(TAG, "Service Connected")
}
override fun onServiceDisconnected(arg0: ComponentName) {
boundService = null
isBound = false
Log.d(TAG, "Service Disconnected")
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity2serviceBinding.inflate(layoutInflater)
setContentView(binding.root)
// Create an intent to start BoundService
boundIntent = Intent(this, BoundService::class.java)
binding.startService.setOnClickListener {
// Start the BoundService
startService(boundIntent)
}
binding.stopService.setOnClickListener {
// Stop the BoundService
stopService(boundIntent)
}
binding.bindService.setOnClickListener {
// Bind to the BoundService
bindService(boundIntent, connection, BIND_AUTO_CREATE)
}
binding.unBindService.setOnClickListener {
if (isBound) {
// Unbind from the BoundService
unbindService(connection)
isBound = false
} else
Toast.makeText(this, "Service is not bound", Toast.LENGTH_SHORT).show()
}
binding.dataFromBoundService.setOnClickListener {
if (isBound) {
// Get data from the bound service
val data = boundService?.getData()
binding.textView.text = data
} else
Toast.makeText(this, "Service is not bound", Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
// Stop the BoundService (prevent memory leaks)
stopService(boundIntent)
super.onDestroy()
}
}
Manifest File (AndroidManifest.xml):
<manifest>
<application>
...
<service android:name=".BoundService"/>
</application>
</manifest>
Conclusion
Services in Android let apps do tasks in the background, keeping the app responsive. There are different types of services: background services run until stopped, bound services let other parts of the app interact with them, and foreground services show notifications to keep users informed. Managing the lifecycle of services (like creating, starting, and destroying them) is important to use resources wisely and avoid issues such as memory leaks. Using threads for long-running tasks prevents the app from freezing. Understanding these concepts helps build better, smoother-running apps.