BroadcastReceivers & System Events
📖 Concept
BroadcastReceivers listen for system-wide or app-internal broadcast events. At the senior level, understand the security model, implicit broadcast restrictions, and modern alternatives.
How BroadcastReceivers work internally:
- An app or the system sends a broadcast Intent via
sendBroadcast() - The Intent goes to ActivityManagerService (AMS) via Binder
- AMS queries PackageManagerService for all registered receivers matching the IntentFilter
- AMS delivers the broadcast to each receiver's
onReceive()method - For manifest-registered receivers, AMS starts the app process if needed
Registration types:
- Manifest-registered (static): Survives app death, limited to specific broadcasts since Android 8.0
- Context-registered (dynamic): Active only while the registering component lives, no restrictions
Android 8.0+ implicit broadcast restrictions: Most implicit broadcasts can no longer wake up manifest-registered receivers. Exceptions: BOOT_COMPLETED, LOCALE_CHANGED, ACTION_TIMEZONE_CHANGED and a few others.
Ordered vs Normal broadcasts:
- Normal: Delivered to all receivers simultaneously (no ordering guarantee)
- Ordered: Delivered one at a time by priority. Receivers can abort propagation.
Modern alternatives:
- For in-app events: Kotlin Flow, LiveData, or EventBus patterns
- For scheduled work: WorkManager
- For push notifications: FCM (Firebase Cloud Messaging)
💻 Code Example
1// Dynamic registration — preferred for most use cases2class NetworkAwareActivity : AppCompatActivity() {3 private val connectivityReceiver = object : BroadcastReceiver() {4 override fun onReceive(context: Context, intent: Intent) {5 val isConnected = intent.getBooleanExtra(6 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false7 ).not()8 handleConnectivityChange(isConnected)9 }10 }1112 override fun onStart() {13 super.onStart()14 val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)15 registerReceiver(connectivityReceiver, filter)16 }1718 override fun onStop() {19 super.onStop()20 unregisterReceiver(connectivityReceiver) // Prevent leak21 }22}2324// Modern approach: Use ConnectivityManager.NetworkCallback instead25class NetworkMonitor @Inject constructor(26 private val connectivityManager: ConnectivityManager27) {28 val isConnected: StateFlow<Boolean> = callbackFlow {29 val callback = object : ConnectivityManager.NetworkCallback() {30 override fun onAvailable(network: Network) { trySend(true) }31 override fun onLost(network: Network) { trySend(false) }32 }33 val request = NetworkRequest.Builder()34 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)35 .build()36 connectivityManager.registerNetworkCallback(request, callback)37 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }38 }.stateIn(CoroutineScope(Dispatchers.Default), SharingStarted.Lazily, false)39}4041// LocalBroadcastManager alternative — use Kotlin Flow42class EventBus {43 private val _events = MutableSharedFlow<AppEvent>(extraBufferCapacity = 64)44 val events: SharedFlow<AppEvent> = _events.asSharedFlow()4546 suspend fun emit(event: AppEvent) { _events.emit(event) }47}
🏋️ Practice Exercise
Practice:
- List 5 implicit broadcasts that still work with manifest-registered receivers on Android 13+
- Implement a BroadcastReceiver that listens for airplane mode changes
- Why is LocalBroadcastManager deprecated? What should you use instead?
- Explain ordered broadcasts — when would you use them?
- Implement a network connectivity monitor using NetworkCallback and Flow
⚠️ Common Mistakes
Not unregistering dynamic receivers — memory leak and potential crash
Doing long-running work in onReceive() — it runs on the main thread and has a 10-second limit before ANR
Using manifest-registered receivers for implicit broadcasts on Android 8.0+ — they won't fire
Using LocalBroadcastManager (deprecated) — use Kotlin Flow or LiveData for in-app events
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for BroadcastReceivers & System Events. Login to unlock this feature.