Understanding Android Broadcast Receivers
What is a Broadcast Receiver?
A Broadcast Receiver in Android is a component that lets your app listen for system-wide or app-specific messages. These messages, called "intents," indicate that an event has happened. For example, your app can listen for system messages like the device booting up or a low battery warning, as well as custom messages from other apps.
How Does It Work?
When an event happens, the Android system creates a broadcast intent that multiple apps can receive. The broadcast can be:
System Broadcasts: Sent by the Android system (e.g.,
ACTION_BOOT_COMPLETED
,ACTION_BATTERY_LOW
).Custom Broadcasts: Sent by your app or other apps.
The Broadcast Receiver acts as a link between the system or other apps and your app, letting it respond to events without always running in the background.
Let's understand how it works
The Broadcast Receiver mechanism in Android uses the Binder IPC framework to send and receive messages between processes.
- A bit about IPC:
IPC stands for inter-process communication. It describes how different Android components talk to each other.
Intents
are messages that components can send and receive. They are used to pass data between processes. With intents, you can start services or activities, invoke broadcast receivers, and more.
Bundles
are data containers passed through intents. They are like serialized objects but faster on Android. You can read a bundle from an intent using thegetExtras()
method.
Binders
allow activities and services to get a reference to another service. This lets you not only send messages to services but also call methods on them directly.
- How broadcast works:
Broadcast Intent Creation:
- When a broadcast is sent, an
Intent
object holds the action and any additional data. TheIntent
specifies what kind of broadcast is being sent and may include extra information as key-value pairs.Broadcast Queue:
- The intent is placed into a queue managed by the system's
ActivityManagerService
(AMS). The AMS is responsible for handling and managing the lifecycle of activities, services, and broadcasts.Identifying Receivers:
- The system identifies all registered receivers that match the intent’s action and other filters (e.g., category, data scheme). These receivers can be either statically registered in the AndroidManifest.xml file or dynamically registered at runtime using
Context.registerReceiver()
.Dispatching:
- The
onReceive()
method of each matching receiver is called. For receivers registered in the application's manifest, theonReceive()
method runs on the main thread of the application. For dynamically registered receivers, theonReceive()
method runs on the main thread of the process where the receiver was registered.
Internal Flow
Sending a Broadcast:
The app or system uses
Context.sendBroadcast()
orContext.sendOrderedBroadcast()
to send a broadcast intent.
val intent = Intent("com.example.SAMPLE_ACTION") sendBroadcast(intent)
Queuing in AMS:
The
ActivityManagerService
(AMS) receives the broadcast request and enqueues it for processing.
// Pseudo-code public void broadcastIntent(Intent intent) { BroadcastRecord r = new BroadcastRecord(intent); mBroadcastQueue.enqueue(r); }
Finding Registered Receivers:
The system looks up all the receivers registered for the broadcast action.
// Pseudo-code List<BroadcastReceiver> matchingReceivers = findReceivers(intent);
Delivering the Broadcast:
The intent is sent to the
onReceive()
method of each matching receiver on the main thread.
// Pseudo-code for (BroadcastReceiver receiver : matchingReceivers) { handler.post(() -> receiver.onReceive(context, intent)); }
Simplified Code Snippets
// Pseudo-code for Broadcast Record
class BroadcastRecord {
Intent intent;
List<BroadcastReceiver> receivers;
BroadcastRecord(Intent intent) {
this.intent = intent;
this.receivers = new ArrayList<>();
}
}
// Pseudo-code for Broadcast Queue
class BroadcastQueue {
Queue<BroadcastRecord> queue = new LinkedList<>();
void enqueue(BroadcastRecord record) {
queue.add(record);
}
void processNext() {
BroadcastRecord record = queue.poll();
for (BroadcastReceiver receiver : record.receivers) {
handler.post(() -> receiver.onReceive(context, record.intent));
}
}
}
Additional Notes:
Threading and Performance:
- The
handler.post
()
method ensures that theonReceive()
method of the receiver runs on the main thread, which is crucial for updating the UI and interacting with other components safely.
- The
Ordered Broadcasts:
- For ordered broadcasts, the system processes each receiver sequentially, allowing each to consume or modify the intent before passing it to the next receiver.
Priority and Permissions:
- The system can prioritize receivers based on their declared priority in the manifest or when dynamically registered. Permissions can also restrict which applications can receive the broadcast.
Let's see some use cases and how to Broadcast and Receive
- Registering Broadcast Receivers:
Broadcast Receivers can be registered in two ways:
Static Registration (in the Manifest):
Suitable for receiving broadcasts even when the app is not running.
Limited to system-defined broadcasts in Android 8.0 (Oreo) and above.
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Dynamic Registration (in Code):
Useful for context-specific broadcasts.
The receiver is active only when the app is running.
val myReceiver = MyBroadcastReceiver()
val filter = IntentFilter(Intent.ACTION_BATTERY_LOW)
registerReceiver(myReceiver, filter)
How to send a broadcast:
val intent = Intent("com.example.SAMPLE_ACTION") intent.putExtra("key", "value") sendBroadcast(intent) // or val intent = Intent("com.example.SAMPLE_ACTION") intent.putExtra("key", "value") sendOrderedBroadcast(intent, null)
Implementing a Broadcast Receiver:
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_BATTERY_LOW -> {
Toast.makeText(context, "Battery is low!", Toast.LENGTH_SHORT).show()
}
Intent.ACTION_BOOT_COMPLETED -> {
Toast.makeText(context, "Device just booted!", Toast.LENGTH_SHORT).show()
}
}
}
}
Example: Monitoring Network Connectivity:
class ConnectivityReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = connectivityManager.activeNetworkInfo
if (activeNetwork?.isConnected == true) {
// Network is connected
Toast.makeText(context, "Connected to the Internet", Toast.LENGTH_SHORT).show()
} else {
// Network is disconnected
Toast.makeText(context, "No Internet connection", Toast.LENGTH_SHORT).show()
}
}
}
// Register the receiver dynamically
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
val receiver = ConnectivityReceiver()
registerReceiver(receiver, filter)
Example: Responding to System Events:
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// Start a service or schedule a job
Toast.makeText(context, "Device booted, starting service", Toast.LENGTH_SHORT).show()
// context.startService(Intent(context, MyService::class.java))
}
}
}
- Communication Between Components:
Broadcast Receivers can talk to other parts of the app using:
Intents: Send data to an activity or service.
LocalBroadcastManager: For communication within the app to avoid security risks of global broadcasts.
// Sending a local broadcast
val intent = Intent("com.example.SOME_ACTION")
intent.putExtra("data", "Hello from the receiver!")
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
// Receiving the local broadcast
class LocalReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val data = intent.getStringExtra("data")
Toast.makeText(context, "Received data: $data", Toast.LENGTH_SHORT).show()
}
}
// Register the local receiver
val localReceiver = LocalReceiver()
val filter = IntentFilter("com.example.SOME_ACTION")
LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver, filter)
Considerations and Best Practices
- Limit Static Broadcast Receivers: To save battery, avoid static registration for broadcasts that happen often.
- Unregister Dynamic Receivers: Always unregister dynamic receivers in
onPause()
oronDestroy()
to avoid memory leaks.
- Use LocalBroadcastManager: For in-app broadcasts, use
LocalBroadcastManager
to improve security and performance.
Conclusion
Broadcast Receivers are a powerful feature in Android, allowing your app to respond to various system and app-specific events. By understanding how they work and following best practices, you can create responsive and efficient Android applications. Happy coding!
That's it for today. Happy Coding...
Subscribe to my newsletter
Read articles from Romman Sabbir directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Romman Sabbir
Romman Sabbir
Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.