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 overridesonReceive()
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
totrue
, else set it tofalse
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.
LocalBroadcastManager
is 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:
Happy Coding ✌️