Understanding Broadcast Receivers in Android

Khush PanchalKhush Panchal
6 min read

What are Broadcast Receivers?

Broadcast Receiver is one of the component in Android that enable apps to listen for and respond to broadcast messages from other apps or the system itself. Think of them as listeners waiting for specific events to occur.

Apps can respond to system-wide events like changes in battery level, network connectivity, and incoming SMS messages by using Broadcast Receivers.

The broadcast message is nothing but an Intent. The action string of this Intent identifies the event that occurred (e.g, android.intent.action.AIRPLANE_MODE indicates that Airplane mode is toggled). 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.

Receiving Broadcast

Apps can receive broadcasts in two ways: Manifest-declared receivers and Context-registered receivers.

Manifest-declared receivers (Static Broadcast Receiver)

If we declare a broadcast receiver in our manifest file, the system launches our app if the app is not already running (Application onCreate() gets triggered) when the broadcast is sent.

Let’s take an example where we have an application that should know whenever there is sms received in the device, to achieve this:

  • Create a class SmsReceiver that extends android BroadcastReceiver class and overrides onReceive() method
//Receiver App
class SmsReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if(intent?.action == "android.provider.Telephony.SMS_RECEIVED") { // it's best practice to verify intent action before performing any operation
            Log.i("ReceiverApp", "SMS Received")
        }
    }
}
  • Add permission to receive sms, then add receiver
<!--Receiver App-->
<uses-permission android:name="android.permission.RECEIVE_SMS"/>

<application>
..
..
  <receiver android:name=".SmsReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
  </receiver>
..
..
</application>

To enable receiver to receive events from outside of our app, set android:exported to true, else set it to false if want to receive locally.

Behind the scenes: The system package manager registers the receiver when the app is installed. The receiver then becomes a separate entry point into our app which means that the system can start the app and deliver the broadcast if the app is not currently running.

Beginning with Android 8.0 (API level 26), we cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don’t target our app specifically). Check this list of Broadcast that can use manifest declared Receivers. However we can always use context-registered receivers.

Context-registered receivers (Dynamic Broadcast Receiver)

Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if we register within an activity context, we receive broadcasts as long as the activity is not destroyed. If we register with the application context, we receive broadcasts as long as the app is running.

To implement context-registered broadcast, remove receiver from manifest file and instead register it inside the activity:

//Receiver App
private val smsReceiver = SmsReceiver()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    registerReceiver(smsReceiver, 
        IntentFilter("android.provider.Telephony.SMS_RECEIVED")
    )
}

override fun onDestroy() {
    super.onDestroy()
    unregisterReceiver(smsReceiver)
}

Custom Broadcasts

Till now we have discussed about system broadcast only, now we will see how we can create and send our own custom broadcast.

Sending broadcasts

Any application can send the broadcast using sendBroadcast(Intent) .

//Sender App
// To send broadcase from any application, specify the custom action to intent and sendBroadcast
val intent = Intent("TEST_CUSTOM_ACTION")
intent.putExtra("data", "Some custom data")
sendBroadcast(intent)

In this way all the applications that are listening to TEST_CUSTOM_ACTION intent will receive the broadcast asynchronously.

We can also limit a broadcast to a particular app by calling setPackage(String) on the intent. In this way broadcast will be send to only single app with mentioned package name.

//Sender App
val intent = Intent("TEST_CUSTOM_ACTION")
intent.putExtra("data", "Some custom data")
intent.setPackage("com.example.receiverapp")
sendBroadcast(intent)

Receiving Broadcasts

To receive broadcast we need to use context-registered receiver (From Android 8.0, we cannot use manifest-declared receiver for our custom broadcast)

//Receiver App
private val customReceiver = CustomReceiver()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("ReceiverApp", "activity created")
        setContentView(R.layout.activity_main)
        registerReceiver(customReceiver,
            IntentFilter("TEST_CUSTOM_ACTION"), RECEIVER_EXPORTED
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("ReceiverApp", "activity destroyed")
        unregisterReceiver(customReceiver)
    }

RECEIVER_EXPORTED flag needs to be add for custom broadcast, it indicates that other apps can send the broadcast to our app. If we do not add this flag, android will throw the below exception:

java.lang.SecurityException: com.example.receiverapp: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn’t being registered exclusively for system broadcasts

Below is out CustomReceiver class that receives the broadcast

//Receiver App
class CustomReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if(intent?.action == "TEST_CUSTOM_ACTION") {
            val value = intent.extras?.getString("data")
            Log.i("ReceiverApp", "Custom Received: $value")
        }

    }
}

Restricting broadcasts with permissions

Permissions allow us to restrict broadcasts to the set of apps that hold certain permissions.

Suppose we want to send broadcast to app that have internet permission, we can specify a permission parameter.

//Sender App
val intent = Intent("TEST_CUSTOM_ACTION")
intent.putExtra("data", "Some custom data")
sendBroadcast(intent, Manifest.permission.INTERNET)

Only receivers who have requested that permission with the tag in their manifest (and subsequently been granted the permission if it is dangerous) can receive the broadcast.

To receive the broadcast, declare permission in manifest file

<!--Receiver App-->
<uses-permission android:name="android.permission.INTERNET"/>
//Receiver App  
registerReceiver(customReceiver,
    IntentFilter("TEST_CUSTOM_ACTION"), Manifest.permission.INTERNET, null, RECEIVER_EXPORTED
)

Additionally we can define our own permission as well for custom broadcast (Check Custom permission)

Ordered Broadcast

There is one more way to send broadcast apart from sendBroadcast(Intent) method, i.e.,sendOrderedBroadcast(Intent, String)

It sends broadcasts to one receiver at a time. Say we have multiple receivers listening to our custom action, when we send broadcast using this method, only one receiver at a time will get the onReceive() callback and other will get only when previous function completely executes. The order receivers run in can be controlled with the android:priority attribute of the matching intent-filter; receivers with the same priority will be run in an arbitrary order.

//Sender App
val intent = Intent("TEST_CUSTOM_ACTION")
intent.putExtra("data", "Some custom data")
sendOrderedBroadcast(intent, null)

//Receiver App
registerReceiver(customReceiver,
    IntentFilter("TEST_CUSTOM_ACTION").apply {
        priority = SYSTEM_HIGH_PRIORITY 
    }, RECEIVER_EXPORTED
 )

Local Broadcast

In case we want to communicate inside our own application only, we can use LocalBroadcastManager class provided by android.

LocalBroadcastManageris a helper class to register for and send broadcasts of Intents to local objects within your process.

This has a number of advantages over sending global broadcasts with Context.sendBroadcast:

  • We know that the data we are broadcasting won’t leave our app, so don’t need to worry about leaking private data.

  • It is not possible for other applications to send these broadcasts to our app, so we don’t need to worry about having security holes they can exploit.

  • It is more efficient than sending a global broadcast through the system.

// Reciever App (Same app will send and receive broadcast)
private val localReceiver = LocalReceiver()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("ReceiverApp", "activity created")
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.btn).setOnClickListener {
            //sending broadcast
            val intent = Intent("TEST_CUSTOM_ACTION_LOCAL")
            LocalBroadcastManager.getInstance(this@MainActivity).sendBroadcast(intent)
        }
        //registering local broadcast
        LocalBroadcastManager.getInstance(this).registerReceiver(localReceiver,
            IntentFilter("TEST_CUSTOM_ACTION_LOCAL")
        )

    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("ReceiverApp", "activity destroyed")
        //unregistering local broadcast
        LocalBroadcastManager.getInstance(this).unregisterReceiver(localReceiver)
    }

Source Code: Github

Contact Me:

LinkedIn, Twitter

Happy Coding ✌️

0
Subscribe to my newsletter

Read articles from Khush Panchal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Khush Panchal
Khush Panchal

Currently working as an Android Engineer at ALLEN Digital, previously worked at MX Player (Acquired by Amazon), a popular video streaming platform with over 1 billion downloads, where I was involved in developing features for the SVOD, Ad Tech and Smart TV domains. I have hands-on experience in all stages of the development cycle, from planning and design to deployment, using Java, Kotlin, Android, XML, Compose, KMP, Exoplayer, MVVM, Flow, and Coroutines. I graduated from IIT Kharagpur, and received the Institute Silver Medal award for the best performance among all the students of the department. I also received the Prof. Sudhir Ranjan Sengupta Memorial Prize for excellence in academics. In addition to my professional work, I actively create open-source projects, with over 600 stars across various repositories, including EventKT (A highly customisable android tracking library) and Ketch (An android file downloader library). I also write technical blogs explaining complex topics with simplicity. I am passionate about learning new technologies, solving challenging problems, and collaborating with diverse teams. I am always interested in exciting projects and potential opportunities to contribute.