Skip to content
intermediate Phase 9 · Background Processing

Broadcast Receivers

Listen for system and app broadcasts with manifest-declared and runtime receivers.

35m
0 problems
Topic Progress 0%

Manifest vs Runtime Broadcast Receivers

What is a Broadcast Receiver?

A broadcast receiver is an Android component that listens for system-wide or app-specific broadcast events. When a matching broadcast is sent, Android wakes up the receiver and delivers the intent.

Two Registration Methods

1. Manifest-declared receivers
Registered in AndroidManifest.xml. The system starts the receiver when the broadcast occurs, even if the app is not running.

<receiver
    android:name=".BootReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            // Schedule periodic work on boot
            WorkManager.getInstance(context)
                .enqueueUniqueWork(
                    "BootSync",
                    ExistingWorkPolicy.KEEP,
                    OneTimeWorkRequestBuilder<BootSyncWorker>().build()
                )
        }
    }
}

2. Runtime-registered receivers
Registered in code using registerReceiver(). Only active while the registering component is alive.

class NetworkActivity : AppCompatActivity() {
    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val isConnected = intent.getBooleanExtra(
                ConnectivityManager.EXTRA_NO_CONNECTIVITY, false
            )
            updateUI(isConnected)
        }
    }

    override fun onStart() {
        super.onStart()
        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        registerReceiver(networkReceiver, filter)
    }

    override fun onStop() {
        super.onStop()
        unregisterReceiver(networkReceiver)
    }
}

Key Differences

Feature Manifest Runtime
Lifecycle App process Component lifecycle
When active Any time (app killed or not) Only while component is alive
Use case Boot completed, SMS received Connectivity, battery changes
Android 8.0+ Most implicit broadcasts removed Preferred method

Android 8.0+ Restrictions

Android 8.0 removed most implicit broadcasts from manifest-declared receivers. Only a whitelist of broadcasts can still be declared in the manifest:

  • ACTION_BOOT_COMPLETED
  • ACTION_LOCALE_CHANGED
  • ACTION_TIMEZONE_CHANGED
  • ACTION_POWER_CONNECTED / ACTION_POWER_DISCONNECTED
  • ACTION_SHUTDOWN

All other implicit broadcasts must be registered at runtime.

System Broadcasts and Best Practices

Common System Broadcasts

Broadcast Action Notes
Boot completed ACTION_BOOT_COMPLETED Manifest only
Connectivity CONNECTIVITY_ACTION Runtime only (Android 7.0+)
Battery low ACTION_BATTERY_LOW Runtime only
Power connected ACTION_POWER_CONNECTED Manifest or runtime
Screen on/off ACTION_SCREEN_ON / ACTION_SCREEN_OFF Runtime only
Time zone changed ACTION_TIMEZONE_CHANGED Manifest or runtime

Exported Receivers

For Android 12+, explicitly set android:exported on all receivers:

<!-- Only your app can trigger this -->
<receiver
    android:name=".InternalReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.myapp.INTERNAL_ACTION" />
    </intent-filter>
</receiver>

<!-- External apps can trigger this -->
<receiver
    android:name=".BootReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Android 13+ Runtime Permission

For runtime receivers listening to ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED, or similar package-related broadcasts, you need the QUERY_ALL_PACKAGES permission or a specific <queries> declaration in the manifest.

LocalBroadcastManager (Deprecated)

LocalBroadcastManager was used for app-internal broadcasts that never leave the process. It is deprecated because StateFlow/SharedFlow and LiveData are better alternatives:

// Old approach (deprecated)
LocalBroadcastManager.getInstance(context)
    .registerReceiver(receiver, IntentFilter("INTERNAL_EVENT"))

// Modern approach — use Flow
class EventBus {
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events

    suspend fun emit(event: Event) {
        _events.emit(event)
    }
}

Best Practices

  1. Always unregister runtime receivers — unregister in onStop() or onDestroy() to avoid memory leaks.
  2. Use exported=false — unless external apps must trigger the receiver.
  3. Use explicit intents — for app-internal broadcasts, avoid implicit broadcasts that could be intercepted.
  4. Check intent action — always verify intent.action before processing.
  5. Prefer Flow over LocalBroadcastManager — for type-safe, lifecycle-aware communication.

Quiz

1. Why did Android 8.0 remove most implicit broadcasts from manifest-declared receivers?

Question 1 options

2. Which broadcast can still be declared in the manifest on Android 8.0+?

Question 2 options

3. What is the recommended modern replacement for LocalBroadcastManager?

Question 3 options

4. When should you use a runtime-registered receiver instead of a manifest-declared one?

Question 4 options

Flashcards

Question

What is the difference between manifest-declared and runtime-registered broadcast receivers?

Answer

Manifest receivers are active even when the app is not running. Runtime receivers are only active while the registering component (Activity, Service) is alive.

Question

Why did Android 8.0 restrict implicit broadcasts from manifest receivers?

Answer

To reduce battery drain and memory usage. Apps would all wake up simultaneously for common broadcasts like connectivity changes, causing unnecessary resource consumption.

Question

What should you always do with a runtime-registered receiver?

Answer

Unregister it in onStop() or onDestroy() to prevent memory leaks. The receiver holds a reference to the registering component.

Question

What does `android:exported="true"` mean on a broadcast receiver?

Answer

External apps can send broadcasts that trigger this receiver. Set it to false unless you need external apps to communicate with your receiver.

Revision Notes

Key Takeaways

  • 1. Android 8.0+ removed most implicit broadcasts from manifest receivers to save battery
  • 2. Runtime receivers must be unregistered to avoid memory leaks
  • 3. LocalBroadcastManager is deprecated — use Flow or LiveData instead
  • 4. Always set android:exported explicitly on receivers for Android 12+ compatibility

Interview Tips

  • Explain the Android 8.0 broadcast restriction and its motivation — battery optimization
  • Know the whitelist of broadcasts still allowed in the manifest
  • Be ready to explain why LocalBroadcastManager is deprecated and what to use instead
  • Mention that runtime receivers are lifecycle-bound — a common source of bugs

Cheat Sheet

Broadcast Receivers Cheat Sheet

Two Registration Methods:

  • Manifest: Active when app is not running (most implicit broadcasts removed in Android 8.0+)
  • Runtime: Active only while component is alive

Android 8.0+ Whitelist (Manifest OK):

  • BOOT_COMPLETED
  • LOCALE_CHANGED
  • TIMEZONE_CHANGED
  • POWER_CONNECTED / POWER_DISCONNECTED
  • SHUTDOWN

Best Practices:

  • Always unregister runtime receivers
  • Use exported=false unless external trigger needed
  • Use explicit intents for app-internal broadcasts
  • Prefer Flow over LocalBroadcastManager (deprecated)

Common System Broadcasts:

  • CONNECTIVITY_ACTION → runtime only
  • BATTERY_LOW → runtime only
  • BOOT_COMPLETED → manifest or runtime
  • SCREEN_ON / SCREEN_OFF → runtime only

Android 13+:

  • QUERY_ALL_PACKAGES needed for package-related broadcasts