Android - Broadcast Receivers
Android - Broadcast Receivers (Updated)
Section titled “Android - Broadcast Receivers (Updated)”Broadcast Receivers are Android components that allow you to register for system-wide or application-specific broadcast messages (Intents). They act as listeners for events originating from the system (e.g., boot completed, battery low) or other applications (e.g., data downloaded). When a registered event occurs, the Android system notifies the appropriate BroadcastReceiver, which can then perform an action.
There are two primary ways to make a BroadcastReceiver work:
- Static Registration (Manifest-declared): The receiver is declared in your app’s
AndroidManifest.xml. The system can launch your app (if not already running) to deliver the broadcast. This method has significant restrictions, especially for implicit broadcasts, on Android 8.0 (API level 26) and higher to save system resources. - Dynamic Registration (Context-registered): The receiver is registered at runtime using
Context.registerReceiver(). It’s only active while the registering component (e.g., an Activity or Service) is alive. This is generally preferred for broadcasts that are only relevant while your app is in the foreground or a specific component is active.
If you are broadcasting custom intents, you will also need to create and send these intents from your application.
Creating the Broadcast Receiver
Section titled “Creating the Broadcast Receiver”A BroadcastReceiver is implemented as a subclass of android.content.BroadcastReceiver. You must override the onReceive() method, where each broadcast message is received as an Intent object.
Here’s a simple example in Kotlin:
package com.example.myapp
import android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.widget.Toast
class MyCustomReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // It's good practice to check context and intent for null, and validate the intent action. if (context != null && intent != null) { val action = intent.action if ("com.example.myapp.CUSTOM_INTENT" == action) { Toast.makeText(context, "Custom Intent Detected!", Toast.LENGTH_LONG).show() // IMPORTANT: onReceive() runs on the main thread. // For long-running tasks, start a Service or use WorkManager. // See: https://developer.android.com/guide/components/broadcasts#effects-process-state } } }}Important Considerations for onReceive():
- Execution Time: The
onReceive()method runs on the main UI thread. It should execute quickly (typically under 10 seconds). For long-running operations, delegate the work to aJobIntentService,WorkManager, or start a foreground service. - Background Execution Limits: Apps targeting Android 8.0 (API 26) and higher have restrictions on background services. If a broadcast receiver triggers background work, ensure it complies with these limits. For example,
goAsync()can be used to allow more time for processing if the work is offloaded to another thread within the receiver’s allowed lifespan.
Registering Broadcast Receiver
Section titled “Registering Broadcast Receiver”1. Static Registration (Manifest-declared)
Section titled “1. Static Registration (Manifest-declared)”To register a receiver in the manifest, add a <receiver> element within the <application> tag in your AndroidManifest.xml. This is suitable for system broadcasts like android.intent.action.BOOT_COMPLETED (though note that apps installed on external storage won’t receive this before Android N if they haven’t been launched once).
<!-- In AndroidManifest.xml --><application ...> <receiver android:name=".MyBootReceiver" android:enabled="true" android:exported="true"> <!-- Set exported to true if it needs to receive broadcasts from outside your app --> <!-- For BOOT_COMPLETED, exported should generally be true --> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
<receiver android:name=".MyCustomReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.myapp.CUSTOM_INTENT" /> </intent-filter> </receiver></application>And the corresponding MyBootReceiver.kt:
package com.example.myapp
import android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.util.Log
class MyBootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_BOOT_COMPLETED == intent.action) { Log.d("MyBootReceiver", "Boot completed event received.") // Perform actions needed on boot, e.g., scheduling alarms or jobs. } }}Manifest Registration Limitations (Android 8.0+):
For apps targeting Android 8.0 (API 26) or higher, the system imposes restrictions on manifest-declared receivers for most implicit broadcasts (broadcasts not specifically targeting your app’s package). There are some exceptions (e.g., BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED). For other implicit broadcasts, you should use context-registered receivers. Explicit broadcasts (where the intent specifies your app’s component) are still delivered to manifest-declared receivers.
2. Dynamic Registration (Context-registered)
Section titled “2. Dynamic Registration (Context-registered)”Dynamically register a receiver using Context.registerReceiver(). This is often done in an Activity’s onResume() or a Service’s onCreate(). Crucially, you must unregister it using Context.unregisterReceiver() when the component is no longer active (e.g., in onPause() for an Activity or onDestroy() for a Service) to prevent memory leaks and unnecessary system load.
// In an Activity (Kotlin)private var myNetworkReceiver: BroadcastReceiver? = null
override fun onResume() { super.onResume() myNetworkReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // Handle network change, e.g., ConnectivityManager.CONNECTIVITY_ACTION // Note: CONNECTIVITY_ACTION also has restrictions on Android N+ // Use NetworkCallback API for more robust network monitoring. Toast.makeText(context, "Network state changed", Toast.LENGTH_SHORT).show() } } val filter = IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION) // For receivers that should only be accessible by your app, you can specify a permission // or set android:exported="false" in manifest if it were manifest-declared. // For context-registered receivers, control visibility using registerReceiver variants. // To make it private to your app if it's listening for system broadcasts that don't require permissions: // No special flag needed, as it's not exported by default. // If registering for a broadcast that requires a permission to send, specify that permission: // registerReceiver(myNetworkReceiver, filter, "com.example.SOME_PERMISSION", null) // For modern Android, use the version that takes an `exported` flag if available: // For Android 13 (API 33) and above, you must specify if the receiver is exported or not. // ContextCompat.registerReceiver(this, myNetworkReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED) // Or if it needs to be exported: // ContextCompat.registerReceiver(this, myNetworkReceiver, filter, ContextCompat.RECEIVER_EXPORTED) // For simplicity with common system broadcasts like CONNECTIVITY_ACTION: registerReceiver(myNetworkReceiver, filter)}
override fun onPause() { super.onPause() myNetworkReceiver?.let { unregisterReceiver(it) myNetworkReceiver = null }}For Android 13 (API 33) and higher, when registering a receiver for unprotected broadcasts, you must specify whether it’s exported or not using flags like Context.RECEIVER_EXPORTED or Context.RECEIVER_NOT_EXPORTED. Use ContextCompat.registerReceiver for compatibility.
A list of common system broadcast actions can be found in Intent.java or the official documentation: https://developer.android.com/reference/android/content/Intent
| Sr.No | Modern Event Monitoring & Description |
|---|---|
| 1 | android.intent.action.BATTERY_CHANGEDSticky broadcast containing battery status. Still usable, but for precise battery level changes consider foreground service or WorkManager for periodic checks if absolutely needed due to background restrictions. |
| 2 | android.intent.action.BOOT_COMPLETEDBroadcast once after system boot. Useful for scheduling tasks. Requires RECEIVE_BOOT_COMPLETED permission. |
| 3 | android.net.conn.CONNECTIVITY_CHANGE (was android.net.ConnectivityManager.CONNECTIVITY_ACTION)Indicates network connectivity changes. For apps targeting N (API 24)+, manifest registration for this is restricted. Use ConnectivityManager.registerNetworkCallback() for robust network monitoring. |
| 4 | android.intent.action.AIRPLANE_MODE (was android.intent.action.AIRPLANE_MODE_CHANGED)Indicates airplane mode has been toggled. Also subject to background restrictions for manifest receivers. |
| 5 | android.intent.action.SCREEN_ON / android.intent.action.SCREEN_OFFUser screen activity. Context-registration is generally required. These are protected intents. |
| 6 | android.intent.action.MY_PACKAGE_REPLACEDSent to your app when it’s updated. Useful for re-scheduling jobs or alarms after an update. |
Always check the official documentation for the specific broadcast action you intend to use, as behavior and restrictions can change across Android versions.
Broadcasting Custom Intents
Section titled “Broadcasting Custom Intents”Your application can generate and send its own custom intents. This is useful for inter-component communication within your app or even between different apps (if designed for it and permissions allow).
To send a normal broadcast, use sendBroadcast(Intent). For ordered broadcasts, use sendOrderedBroadcast(...).
// In an Activity or Service (Kotlin)fun broadcastCustomIntent() { val intent = Intent() // Action should be unique, typically using your package name as a prefix. intent.action = "com.example.myapp.CUSTOM_INTENT" intent.putExtra("data", "Some important value") // Optional: add data // To restrict the broadcast to components within your app: intent.setPackage(packageName) // Highly recommended for security if broadcast is internal sendBroadcast(intent)
// If the receiver needs a permission to receive this broadcast: // sendBroadcast(intent, "com.example.myapp.permission.RECEIVE_CUSTOM_INTENT")}LocalBroadcastManager (Deprecated but worth knowing concept):
For sending broadcasts only within your app, LocalBroadcastManager (from androidx.localbroadcastmanager:localbroadcastmanager) was previously recommended. It’s more efficient and secure as intents don’t leave your app. However, LocalBroadcastManager is now deprecated. The official recommendation is to use other observable patterns like LiveData, Flow (Kotlin Coroutines), or event bus libraries for in-app communication, as they are more lifecycle-aware and testable. If you need a direct replacement for app-local broadcast behavior and cannot refactor, you can still use global sendBroadcast with Intent.setPackage(packageName) to restrict it to your app.
Note on Sticky Broadcasts: The sendStickyBroadcast(Intent) method is deprecated since API level 21. Sticky broadcasts were problematic due to security and performance implications. Avoid them. If you need similar behavior, consider using a foreground service that other components can bind to, or persist the state and have components query it when they become active.
Example: Custom Intent Broadcast and Reception
Section titled “Example: Custom Intent Broadcast and Reception”This example demonstrates broadcasting a custom intent from an Activity and receiving it with a dynamically registered BroadcastReceiver.
-
Create the BroadcastReceiver (
MyCustomReceiver.kt- shown earlier): Ensure it listens for"com.example.myapp.CUSTOM_INTENT". -
Modify
MainActivity.ktto register the receiver and send the broadcast:
package com.example.myapp
import androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.content.Intentimport android.content.IntentFilterimport android.widget.Buttonimport androidx.core.content.ContextCompat // For registerReceiver compatibility
class MainActivity : AppCompatActivity() {
private var customReceiver: MyCustomReceiver? = null private val CUSTOM_INTENT_ACTION = "com.example.myapp.CUSTOM_INTENT"
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Assuming a layout with a button
val sendBroadcastButton: Button = findViewById(R.id.sendBroadcastButton) sendBroadcastButton.setOnClickListener { broadcastIntent() } }
override fun onResume() { super.onResume() customReceiver = MyCustomReceiver() val intentFilter = IntentFilter(CUSTOM_INTENT_ACTION) // For Android 13+ target, specify exported flag. Using ContextCompat for older versions. ContextCompat.registerReceiver(this, customReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED) }
override fun onPause() { super.onPause() customReceiver?.let { unregisterReceiver(it) customReceiver = null } }
private fun broadcastIntent() { val intent = Intent(CUSTOM_INTENT_ACTION) intent.putExtra("message", "Hello from MainActivity!") // Restrict to this app's package for security intent.setPackage(packageName) sendBroadcast(intent) }}- Layout file (
res/layout/activity_main.xml): Add a button.
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center">
<Button android:id="@+id/sendBroadcastButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Custom Broadcast" />
</LinearLayout>- No manifest registration needed for
MyCustomReceiverin this dynamic example. If you were to use manifest registration for the custom intent, you’d add it toAndroidManifest.xmlas shown previously, settingandroid:exported="false"if it’s only for internal app use.
Run the app. Clicking the button will send the custom broadcast, and MyCustomReceiver will display a Toast message. This demonstrates a basic flow of custom intent broadcasting and reception within an app using modern practices.
For more complex scenarios or inter-app communication, carefully consider permissions, intent filters, and security implications (e.g., signature-based permissions for trusted communication between your own apps).