Working with BroadcastReceivers
1. Creating BroadcastReceiver Class
| Property | Description | Example |
|---|---|---|
| onReceive(Context, Intent) | Callback method invoked when broadcast is received | |
Example: Basic BroadcastReceiver
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == Intent.ACTION_BOOT_COMPLETED) {
// Handle boot completed
}
}
}
Note: BroadcastReceiver must be declared in
AndroidManifest.xml or registered dynamically.2. Registering Receivers in Manifest
| Property | Description | Values |
|---|---|---|
| receiver | Declares a broadcast receiver | <receiver> |
| android:name | Full class name of the receiver | com.example.MyReceiver |
| android:enabled | Enables or disables the receiver | true | false |
| android:exported | Allows other apps to interact with the receiver | true | false |
| android:permission | Permission required to send broadcasts to this receiver | android.permission.SEND_SMS |
Example: Declaring a BroadcastReceiver in AndroidManifest.xml
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Note: For
android:exported, set to true only if the receiver should be accessible by other apps. Otherwise, set to false for internal use.3. Registering Receivers Dynamically
| Method | Description | Notes |
|---|---|---|
| registerReceiver() | Registers a receiver dynamically at runtime | Must be unregistered to prevent memory leaks |
| unregisterReceiver() | Unregisters a receiver to prevent memory leaks | Must be called in the same context where it was registered |
Example: Registering a Dynamic Receiver
class MainActivity : AppCompatActivity() {
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Handle broadcast
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val filter = IntentFilter("com.example.CUSTOM_ACTION")
registerReceiver(receiver, filter)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
}
Note: Dynamic registration is only valid while the component (e.g., Activity) is alive. Once the component is destroyed, the receiver must be unregistered.
4. Unregistering Receivers
Unregistering a BroadcastReceiver is crucial to prevent memory leaks and ensure proper resource management. Android requires explicit unregistration of receivers that were registered programmatically.
| Method | Description | Notes |
|---|---|---|
| unregisterReceiver() | Unregisters a previously registered BroadcastReceiver. |
Must be called on the same context used for registration. |
Example: Unregistering a BroadcastReceiver
class MainActivity : AppCompatActivity() {
private val receiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Register the receiver
registerReceiver(receiver, IntentFilter("com.example.MY_ACTION"))
}
override fun onDestroy() {
super.onDestroy()
// Unregister the receiver to prevent memory leaks
unregisterReceiver(receiver)
}
}
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Handle broadcast
}
}
Note: If a
BroadcastReceiver is registered in the manifest, it does not need to be unregistered manually.
Warning: Failing to unregister a dynamically registered
BroadcastReceiver can lead to memory leaks and unexpected behavior.5. Sending Broadcasts
| Method | Description | Notes |
|---|---|---|
| sendBroadcast() | Sends a broadcast to all registered receivers | Async, no result expected |
| sendOrderedBroadcast() | Sends a broadcast to receivers in order | Allows for result handling and aborting |
| sendStickyBroadcast() | Sends a broadcast and keeps it in the system | DEPRECATED since API 21 |
Example: Sending a Basic Broadcast
val intent = Intent("com.example.CUSTOM_ACTION")
intent.putExtra("message", "Hello from BroadcastReceiver")
sendBroadcast(intent)
Example: Sending Ordered Broadcast
val intent = Intent("com.example.ORDERED_ACTION")
intent.putExtra("data", "ordered data")
sendOrderedBroadcast(intent, null) { _, _ ->
// Handle result if needed
}
Note: For security, use
LocalBroadcastManager or Context.registerReceiver() for local broadcasts.
6. Handling System Broadcasts
| Broadcast | Permission | Description |
|---|---|---|
| android.intent.action.BATTERY_CHANGED | android.permission.BATTERY_STATS |
Provides information about the battery status, including level, health, and plugged state. |
| android.intent.action.BATTERY_LOW | android.permission.BATTERY_STATS |
Indicates that the battery is low and the device should enter power-saving mode. |
| android.intent.action.BATTERY_OKAY | android.permission.BATTERY_STATS |
Indicates that the battery is now at a normal level after being low. |
| android.intent.action.BOOT_COMPLETED | android.permission.RECEIVE_BOOT_COMPLETED |
Indicates that the system has completed booting and is ready to receive broadcasts. |
| android.intent.action.SCREEN_ON | android.permission.RECEIVE_BOOT_COMPLETED |
Indicates that the screen has turned on. |
| android.intent.action.SCREEN_OFF | android.permission.RECEIVE_BOOT_COMPLETED |
Indicates that the screen has turned off. |
Example: Receiving Battery Status Changes
class BatteryReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BATTERY_CHANGED == intent.action) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPct = (level / scale) * 100
Log.d("Battery", "Battery Level: $batteryPct%")
}
}
}
Example: Registering for Boot Completion
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
// Start background service or perform initialization tasks
val serviceIntent = Intent(context, MyBackgroundService::class.java)
context.startService(serviceIntent)
}
}
}
Note: To receive system broadcasts like
BATTERY_CHANGED, you must declare the appropriate permissions in your manifest and ensure that the receiver is registered either in code or in the manifest.
7. Setting Broadcast Permissions
| Permission | Description | Required |
|---|---|---|
| android.permission.RECEIVE_BOOT_COMPLETED | Allows application to receive the BOOT_COMPLETED system broadcast | Yes, for boot-related broadcasts |
| android.permission.ACCESS_COARSE_LOCATION | Allows application to access approximate location | Yes, for location-related broadcasts |
| android.permission.ACCESS_FINE_LOCATION | Allows application to access precise location | Yes, for location-related broadcasts |
| android.permission.BLUETOOTH_ADMIN | Allows applications to discover and pair Bluetooth devices | Yes, for Bluetooth-related broadcasts |
| android.permission.INTERNET | Allows application to open network sockets | Yes, for network-related broadcasts |
| android.permission.READ_PHONE_STATE | Allows read only access to phone state | Yes, for phone state change broadcasts |
Example: Declaring Permissions in Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application>
...
</application>
</manifest>
Note: Permissions must be declared in the
AndroidManifest.xml file for broadcast receivers to function properly.
8. Creating Custom Broadcasts
| Property | Description | Example |
|---|---|---|
| action | Unique string identifier for the broadcast | "com.example.CUSTOM_ACTION" |
| extras | Bundle of data to send with the broadcast | Intent.putExtra("message", "Hello") |
Example: Sending a Custom Broadcast
val intent = Intent("com.example.CUSTOM_ACTION")
intent.putExtra("message", "Hello from BroadcastReceiver!")
sendBroadcast(intent)
Note: Custom broadcasts are sent using
sendBroadcast() and can be received by any registered receiver in the app.
9. Using Implicit vs Explicit Broadcasts
| Aspect | Implicit Broadcast | Explicit Broadcast |
|---|---|---|
| Intent | Intent(intentAction) |
Intent(context, ReceiverClass::class.java) |
| Registration | Declared in AndroidManifest.xml |
Registered programmatically using registerReceiver() |
| Scope | Global to the app or system | Targeted to specific receiver(s) |
| Security | Less secure; can be intercepted | More secure; only known receivers receive |
| Performance | Higher overhead due to system-wide dispatch | Better performance; direct delivery |
Example: Sending an Implicit Broadcast
val intent = Intent("com.example.CUSTOM_ACTION")
intent.putExtra("data", "someValue")
sendBroadcast(intent)
Example: Sending an Explicit Broadcast
val intent = Intent(this, MyReceiver::class.java)
intent.putExtra("data", "someValue")
sendBroadcast(intent)
Note: As of Android 8.0 (API level 26), implicit broadcasts for system events are restricted to improve battery life and security.
10. Handling Broadcast Exceptions
| Exception Type | Description | Handling Strategy |
|---|---|---|
| SecurityException | Thrown when the app does not have permission to receive a broadcast. | Ensure proper permissions are declared in AndroidManifest.xml. |
| IllegalStateException | Occurs when trying to register a receiver after the component has been destroyed. | Register receivers in onResume() and unregister in onPause(). |
| RuntimeException | Generic exception for runtime issues in broadcast handling. | Wrap receiver logic in try-catch blocks to handle gracefully. |
Example: Exception Handling in BroadcastReceiver
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
try {
// Handle broadcast logic here
val data = intent?.getStringExtra("data")
if (data == null) {
throw IllegalArgumentException("Data is null")
}
} catch (e: SecurityException) {
Log.e("BroadcastReceiver", "Security error: ${e.message}")
} catch (e: IllegalStateException) {
Log.e("BroadcastReceiver", "Illegal state error: ${e.message}")
} catch (e: RuntimeException) {
Log.e("BroadcastReceiver", "Runtime error: ${e.message}")
}
}
}
Note: Always log exceptions for debugging and avoid letting unhandled exceptions crash the app.