Simplifying Background Tasks with Android Services & Broadcast Receivers

Simplifying Background Tasks with Android Services & Broadcast Receivers

In-Depth Walkthrough on Enhancing Task Management and Event Handling in Android Apps

Services in Android

Service is an Application Component, that can perform long-running operations in the background.

Doesn't provide a user interface & can be invoked from another application component. Eg - Download File Service starts from the Button Component.

Types of Services

  1. Foreground Services
    The Process is visible to the user. Hence they display a notification E.g. - Downloading a File (Seek-bar shows progress) or playing an audio track.

     startForegroundService(Intent(this, MyService::class.java))
    
  2. Background Services

    The Process isn't visible to the user. E.g. - Gallery compressing images to store them.

     startService(Intent(this, MyService::class.java))
    
  3. Bound Services

    Client-Server Relation between component and service, hence doesn't run in the background. A bound service runs only as long as another application component is bound to it. E.g. - Music Notification to Pause/Play while the app is running in background.

    Service Class

    Traditional Service Class, which uses the main thread of the application. Android OS gives each application a share of the processor, hence slowing down the application when the service has high requirements or is large.

    E.g. - Downloading Slows down when you are simultaneously using that application, as the share of the service is limited.

     <!--Defining the service in AndroidManifest.xml file -->
     <application> .. 
         <service android:name = "ClassicServiceExample"/>
     </application>
    
     //MainActivit.kt
     btn.setOnClickListener { 
         val intent = Intent(this@MainActivity, ClassicServiceExample::class.java)
         startService(intent)
         ...
         stopService(intent)    //runs onDestroy() method of service. 
     }
    
     //Creating a new Kotlin Class :  ClassicServiceExample.kt
     //extending it with our Service Class & Implementing its members
     class ClassServiceExample : Service(){
         override fun onBind(p0: Intent?) : IBinder? {
             return null
             //as we are on not implementing a Bound Service
         }
    
         override fun onStartCommand(intent : Intent?, flags : Int, startId: Int) : Int {
             //write service here - runs when service started
             //stopSelf() method when called inside this function, 
             //will destory the service & go to onDestroy() callback
             return super.onStartCommand(intent, flags, startId)
         }
    
         override fun onDestroy(){
             //runs when service destroyed
             super.onDestroy()
         }
     }
    

    Return Value to onStartCommand() determines how the Android system should handle a service if it gets killed due to low memory or other constraints.

    • START_STICKY - system will restart service as soon as resources available

    • START_NOT_STICKY - system won’t restart service

    • START_REDELIVER_INTENT - system restarts task it was handling

    //Implementation Example - 
    private lateinit var player : MediaPlayer //used to play audio and video in Android
    override fun onStartCommand(intent : Intent?, flags : Int, startId: Int) : Int {
        player = MediaPlayer.create(this, Settings.System.DEFAULT_RINGTONE_URI)
    //initializes a MediaPlayer instance for a specific audio source.
    //paramenters - context, URI to ringtone resource
        player.isLooping = true
        player.start()

        return START_STICKY  
    }
    override fun onDestroy(){
        //runs when service destroyed
        super.onDestroy()
        player.stop()
    }

Broadcast Receivers in Android

A messaging system that allows communication between the Android OS and Android applications, as well as between different applications. This system is used to send messages regarding system events such as booting, charging, battery indication, and more.

Application can register to receive specific broadcasts. Whenever a system event occurs, a broadcast is sent from the OS or an application, and the system automatically routes broadcasts to apps that have subscribed to receive that particular type of broadcast.

The broadcast message itself is wrapped in an Intent object whose action string identifies the event that occurred (for example android.intent.action.AIRPLANE_MODE). The intent may also include additional information bundled into its extra field. For example, the airplane mode intent includes a boolean extra that indicates whether or not Airplane Mode is on.

Some important wide-generated events:

  1. android.intent.action.BATTERY_LOW: Indicates low battery condition on the device.

  2. android.intent.action.BOOT_COMPLETED: This is broadcast once after the system has finished booting

  3. android.intent.action.CALL: To perform a call to someone specified by the data

  4. android.intent.action.DATE_CHANGED: The date has changed

  5. android.intent.action.REBOOT: Have the device reboot

Custom Broadcasts can also be enabled which are send by applications to communicate specific events.

Creating a Broadcast Receiver

  1. Define a BroadcastReceiver class: Extend the BroadcastReceiver class & override the onReceive() method to define the actions to perform when a broadcast is received.

  2. Register the BroadcastReceiver:

    • Static Registration: Declare the receiver in the AndroidManifest.xml
    //AndroidManifest.xml
    <receiver android:name=".MyBroadcastReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    class MyBroadcastReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
    //intent.action is a constant
    //representing a specific type of broadcast event or action.
                Log.d("BroadcastReceiver", "Device Boot Completed!")
                // Perform your action here
            }
        }
    }
  • Dynamic Registration: Register the receiver programmatically at runtime using registerReceiver().
    class MainActivity : AppCompatActivity() {
        private val myReceiver = object : BroadcastReceiver() {
    //define an implementation of the BroadcastReceiver class.
            override fun onReceive(context: Context, intent: Intent) {
                Log.d("BroadcastReceiver", "Custom Broadcast Received!")
            }
        }

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    // Create an IntentFilter to listen for specific broadcasts
    // class used to specify the type of broadcasts a BroadcastReceiver
    // should listen to.
            val filter = IntentFilter("com.example.CUSTOM_BROADCAST")
            registerReceiver(myReceiver, filter)
    //used to dynamically register a BroadcastReceiver at runtime.
    //Links BroadcastReceiver with IntentFilter to start listening for broadcasts.
        }

        override fun onDestroy() {
            super.onDestroy()
            // Unregister the receiver to avoid memory leaks
            unregisterReceiver(myReceiver)
        }
    }
    //Sending a Broadcast 
    val intent = Intent("com.example.CUSTOM_BROADCAST")
    sendBroadcast(intent)