Topic 7: Android Building Blocks -III : Broadcast Receivers
📖 9 min read · 🎯 intermediate · 🧭 Prerequisites: android-building-blocks-ii-services, encapsulation-in-java
Why this matters
Picture this: your phone's battery drops to 15% and suddenly three different apps adjust their behavior — one pauses a sync, another dims its display, one sends you a nudge. None of those apps were "talking" to each other. They were all just listening. That's the magic of BroadcastReceiver in Android — your app can quietly register to hear system-wide events like low battery, airplane mode changes, or incoming SMS, and spring into action the moment they happen. In this topic, we learn exactly how to wire that up.
What You'll Learn
- What Broadcast Receivers are and why Android uses a system-wide broadcast model
- The difference between Normal Broadcasts and Ordered Broadcasts
- How to register a receiver statically in
AndroidManifest.xmland dynamically at runtime withContext.registerReceiver() - How to define, send, and receive custom broadcasts within your own application
- How to control broadcast propagation using
android:priorityandabortBroadcast()
The Analogy
Think of Android broadcasts as a city's emergency radio frequency. When the fire department detects a hazard (say, the battery runs low), they transmit a signal on a known channel. Only radios that are tuned to that channel — registered receivers — will pick up the message and activate their sirens. Normal broadcasts are like an open FM signal: every tuned radio hears it simultaneously, in whatever order the airwaves carry the signal. Ordered broadcasts are like a chain of command: the general hears the message first, acts on it, then passes it down to the colonel — unless the general decides the message stops there and calls abortBroadcast().
Chapter 1: Overview of Broadcast Receivers
A BroadcastReceiver is an Android component that allows your application to respond to system-wide broadcast announcements. These broadcasts can originate from:
- The system itself — low battery, Wi-Fi state changes, boot completed, screen on/off, etc.
- Other applications — any app can send a broadcast that other apps listen for.
- Your own application — an activity can broadcast an intent that another component in the same app receives.
When a broadcast is sent, the Android system identifies all registered receivers for that broadcast action and notifies each of them by calling their onReceive() method.
Types of Broadcasts
| Type | Delivery | Order | Can Abort? |
|---|---|---|---|
| Normal Broadcast | Asynchronous | Any order | No |
| Ordered Broadcast | Synchronous | Priority-based | Yes |
- Normal Broadcasts — sent with
sendBroadcast(). Delivered asynchronously to all receivers roughly simultaneously. No receiver can pass results to the next, and none can abort the chain. - Ordered Broadcasts — sent with
sendOrderedBroadcast(). Delivered synchronously, one receiver at a time, in descendingandroid:priorityorder. Each receiver can inspect or modify the result, pass it to the next, or callabortBroadcast()to terminate delivery entirely.
Chapter 2: Registering Broadcast Receivers
There are two registration strategies, each with distinct lifecycle implications.
Static Registration
Defined in AndroidManifest.xml. The receiver is always active, even if your app is not running in the foreground. Use this for broadcasts your app must never miss, such as BOOT_COMPLETED.
Pros: Always listening, survives app restarts.
Cons: Can drain battery; some system broadcasts (like ACTION_BATTERY_CHANGED) cannot be received statically.
Dynamic Registration
Registered at runtime using Context.registerReceiver() and unregistered with Context.unregisterReceiver(). The receiver is only active while your component (usually an Activity) is alive. Always unregister in the matching lifecycle method to avoid memory leaks.
Pros: Tied to component lifecycle; receives broadcasts that static registration cannot (e.g., ACTION_BATTERY_CHANGED).
Cons: Stops listening when the component is destroyed.
Chapter 3: Example — Battery Low Broadcast Receiver
Static Registration
Step 1: Create the Broadcast Receiver
BatteryLowReceiver.java
package com.example.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class BatteryLowReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_LOW.equals(intent.getAction())) {
Toast.makeText(context, "Battery is low!", Toast.LENGTH_LONG).show();
}
}
}
Step 2: Register in AndroidManifest.xml
<receiver android:name=".BatteryLowReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_LOW"/>
</intent-filter>
</receiver>
Dynamic Registration
The same logic registered and unregistered within the activity lifecycle:
MainActivity.java
package com.example.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver batteryLowReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_LOW.equals(intent.getAction())) {
Toast.makeText(context, "Battery is low!", Toast.LENGTH_LONG).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_LOW);
registerReceiver(batteryLowReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(batteryLowReceiver);
}
}
Key lifecycle pairing: registerReceiver() in onCreate() → unregisterReceiver() in onDestroy(). Failing to call unregisterReceiver() causes a memory leak and a runtime exception when the activity is garbage collected.
Chapter 4: Example — Custom Broadcast Receiver
Your application can define its own broadcast action string, send it, and receive it — enabling loose-coupled communication between components.
Step 1: Define the Custom Broadcast Receiver
CustomReceiver.java
package com.example.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class CustomReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.myapp.CUSTOM_BROADCAST".equals(intent.getAction())) {
Toast.makeText(context, "Custom Broadcast Received!", Toast.LENGTH_LONG).show();
}
}
}
Step 2: Register in AndroidManifest.xml
<receiver android:name=".CustomReceiver">
<intent-filter>
<action android:name="com.example.myapp.CUSTOM_BROADCAST"/>
</intent-filter>
</receiver>
Step 3: Send the Custom Broadcast
MainActivity.java
package com.example.myapp;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.send_broadcast_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.myapp.CUSTOM_BROADCAST");
sendBroadcast(intent);
}
});
}
}
Step 4: Main Activity Layout
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/send_broadcast_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Custom Broadcast" />
</LinearLayout>
Tapping the button fires sendBroadcast() with the custom action string. Any registered receiver — static or dynamic — listening for com.example.myapp.CUSTOM_BROADCAST will have its onReceive() called.
Chapter 5: Ordered Broadcasts
Ordered broadcasts are delivered to one receiver at a time, in descending android:priority order. A receiver with priority="2" fires before one with priority="1". Each receiver can call abortBroadcast() to prevent any lower-priority receivers from seeing the message.
OrderedReceiver1.java (lower priority — fires second)
package com.example.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class OrderedReceiver1 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Ordered Receiver 1 received the broadcast", Toast.LENGTH_SHORT).show();
// Uncomment the line below to stop the broadcast from being propagated further
// abortBroadcast();
}
}
OrderedReceiver2.java (higher priority — fires first)
package com.example.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class OrderedReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Ordered Receiver 2 received the broadcast", Toast.LENGTH_SHORT).show();
}
}
Register Receivers in AndroidManifest.xml
<receiver android:name=".OrderedReceiver1" android:priority="1">
<intent-filter>
<action android:name="com.example.myapp.ORDERED_BROADCAST"/>
</intent-filter>
</receiver>
<receiver android:name=".OrderedReceiver2" android:priority="2">
<intent-filter>
<action android:name="com.example.myapp.ORDERED_BROADCAST"/>
</intent-filter>
</receiver>
OrderedReceiver2 (priority 2) runs first, then OrderedReceiver1 (priority 1). Uncommenting abortBroadcast() inside OrderedReceiver1 would have no effect since it is already the last in the chain — but if placed in OrderedReceiver2, it would prevent OrderedReceiver1 from ever seeing the message.
Send Ordered Broadcast
MainActivity.java
package com.example.myapp;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.send_ordered_broadcast_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.myapp.ORDERED_BROADCAST");
sendOrderedBroadcast(intent, null);
}
});
}
}
The second parameter of sendOrderedBroadcast(intent, null) is an optional permission string. Pass null if no receiver permission is required.
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/send_ordered_broadcast_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Ordered Broadcast" />
</LinearLayout>
Ordered Broadcast Flow
flowchart TD
A[sendOrderedBroadcast called] --> B[OrderedReceiver2\npriority=2]
B -->|passes broadcast| C[OrderedReceiver1\npriority=1]
B -->|calls abortBroadcast| D[Broadcast terminated\nReceiver1 never fires]
C --> E[Delivery complete]
🧪 Try It Yourself
Task: Build a self-contained app that sends a custom broadcast called com.example.myapp.GREETING_BROADCAST when a button is pressed, and receives it with a dynamically registered receiver that shows a Toast saying "Hello from the broadcast!".
Success criterion: Pressing the button causes the toast to appear. Then, override onDestroy() to unregister the receiver — verify in Logcat that no ReceiverCallNotAllowedException or leak warning appears.
Starter snippet — dynamic registration in MainActivity.java:
private BroadcastReceiver greetingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.myapp.GREETING_BROADCAST".equals(intent.getAction())) {
Toast.makeText(context, "Hello from the broadcast!", Toast.LENGTH_LONG).show();
}
}
};
// In onCreate():
IntentFilter filter = new IntentFilter("com.example.myapp.GREETING_BROADCAST");
registerReceiver(greetingReceiver, filter);
// In onDestroy():
unregisterReceiver(greetingReceiver);
Wire up a button that calls sendBroadcast(new Intent("com.example.myapp.GREETING_BROADCAST")).
🔍 Checkpoint Quiz
Q1. What is the key behavioral difference between sendBroadcast() and sendOrderedBroadcast()?
A) sendBroadcast() requires static registration; sendOrderedBroadcast() requires dynamic registration
B) sendBroadcast() delivers asynchronously to all receivers in any order; sendOrderedBroadcast() delivers synchronously one receiver at a time by priority
C) sendOrderedBroadcast() can only target system receivers
D) There is no difference; both methods produce identical delivery behavior
Q2. Given this manifest snippet, which receiver fires first when com.example.myapp.ORDERED_BROADCAST is sent as an ordered broadcast?
<receiver android:name=".ReceiverA" android:priority="1"> ... </receiver>
<receiver android:name=".ReceiverB" android:priority="5"> ... </receiver>
<receiver android:name=".ReceiverC" android:priority="3"> ... </receiver>
A) ReceiverA
B) ReceiverB
C) ReceiverC
D) All three fire simultaneously
Q3. A developer registers a BroadcastReceiver dynamically in onCreate() but never calls unregisterReceiver(). What is the most likely consequence?
A) The receiver stops working after the first broadcast
B) The app crashes immediately on launch
C) A memory leak occurs and Android may throw a runtime exception when the activity is garbage collected
D) The receiver automatically unregisters when the app goes to background
Q4. You need your app to detect Intent.ACTION_BATTERY_LOW only while the user has your activity open, and you do not need it to fire when the app is in the background. Which approach is correct?
A) Register statically in AndroidManifest.xml with the BATTERY_LOW action
B) Register dynamically with registerReceiver() in onCreate() and unregister in onDestroy()
C) Use sendBroadcast() from the activity
D) Override onResume() to query battery level directly from BatteryManager
A1. B — sendBroadcast() is asynchronous and delivers to all matching receivers in an unspecified order simultaneously. sendOrderedBroadcast() is synchronous and delivers to receivers one at a time in priority order, allowing each receiver to abort the chain.
A2. B — ReceiverB has android:priority="5", the highest value, so it fires first. Priority is evaluated in descending order: B (5) → C (3) → A (1).
A3. C — Failing to call unregisterReceiver() leaks the receiver reference. Android may also throw IllegalArgumentException when it tries to clean up the activity. Always pair registerReceiver() with unregisterReceiver() in the corresponding lifecycle method.
A4. B — Dynamic registration ties the receiver's lifetime to the activity. ACTION_BATTERY_CHANGED and ACTION_BATTERY_LOW are sticky broadcasts that also cannot be received via static registration in newer Android versions, making dynamic registration the correct and only viable choice here.
🪞 Recap
- A
BroadcastReceiverresponds to system-wide or app-defined intent broadcasts via itsonReceive()callback. - Normal broadcasts (
sendBroadcast()) are asynchronous and deliver to all matching receivers in any order; ordered broadcasts (sendOrderedBroadcast()) are synchronous and deliver one receiver at a time byandroid:priority. - Static registration in
AndroidManifest.xmlkeeps the receiver alive even when the app is not running; dynamic registration withContext.registerReceiver()ties the receiver to a component's lifecycle. - Always pair
registerReceiver()withunregisterReceiver()— forgetting to unregister causes memory leaks. - Custom broadcasts are defined by a unique action string (e.g.,
com.example.myapp.CUSTOM_BROADCAST) and sent withsendBroadcast()orsendOrderedBroadcast().
📚 Further Reading
- Android Broadcasts — official docs — the authoritative guide on broadcast mechanics, implicit vs. explicit, and security best practices
- BroadcastReceiver API reference — full method listing including
abortBroadcast(),getResultData(), andsetResultData() - Intent and IntentFilter — how Android matches broadcast actions to registered receivers
- ⬅️ Previous: Android Building Blocks -II : Services
- ➡️ Next: Polymorphism in Java