Understanding Broadcast Receivers in Android

Understanding Broadcast Receivers in Android

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 ✌️