
You’ve tested your app on a Pixel, and notifications arrive instantly. Then users on Xiaomi or Samsung devices report silence. The problem isn’t your code—it’s Android’s power-saving layers and OEM customisations. These systems prioritise battery life over real-time delivery, and they don’t log failures. If you don’t account for them, your notifications vanish without trace, and your engagement metrics drop without explanation. Here’s how to fix it.
How Doze mode turns your notifications into ghosts
Doze mode starts when a device is unplugged, stationary, and the screen is off. It has three phases: Active, Light, and Deep. Active Doze (API 23+) allows normal app behaviour. Light Doze (API 23+) defers jobs and alarms but still allows high-priority Firebase Cloud Messages (FCM). Deep Doze (API 23+) blocks all network access and defers FCM unless it’s marked as high-priority.
In Deep Doze, your app’s background tasks are paused. A banking app’s fraud alert, sent via normal-priority FCM, won’t arrive until the device exits Doze—often hours later. High-priority FCM bypasses this, but only if the app meets two conditions: it has a foreground service running, or the user interacted with it in the last 5 minutes. If neither is true, even high-priority FCM is deferred.
The trade-off is clear: battery life or real-time delivery. To measure the impact, log when your app enters and exits Doze using PowerManager.isDeviceIdleMode(). Track how many users experience Deep Doze during your app’s peak usage hours. If 30% of your daily active users (DAU) are in Deep Doze when you send a notification, expect a 30% drop in engagement. Use this data to decide whether to optimise for battery life or push for high-priority FCM.
Why OEM battery managers kill your app before Android does
Xiaomi, Huawei, Samsung, and Oppo add their own battery managers on top of Android’s Doze mode. These managers aggressively kill background processes to extend battery life. Xiaomi’s MIUI, for example, has an "Auto-start" whitelist. If your app isn’t on it, MIUI will block your background services, including FCM, after a few hours of inactivity. Huawei’s EMUI does the same with its "Protected Apps" list, and Samsung’s One UI uses "Background Restrictions".
You can detect if your app is on the auto-start list using the PackageManager API. On Xiaomi devices, check for the XIAOMI_AUTO_START permission. If it’s not granted, your app won’t receive FCM messages in the background. A fitness app’s step-count sync failed for Xiaomi users after an MIUI update because the app wasn’t whitelisted. The fix was to prompt users to enable auto-start, but this adds friction and reduces adoption.
The trade-off is between asking users to manually whitelist your app or silently losing engagement. If you prompt users, expect a 20-30% drop-off in completions. If you don’t, expect a 40-50% drop in background engagement on affected devices. Log which OEMs your users have and how many are on restrictive battery managers. Use this to decide whether to invest in OEM-specific fixes or accept the loss.
When WorkManager is the wrong tool for the job
WorkManager is designed for deferrable tasks, not real-time notifications. It enforces back-off policies, network conditions, and charging state checks. On Android 12+, a 15-minute periodic task can be delayed up to 1 hour if the device is in Doze mode or low-power state. WorkManager also respects the app’s battery optimisation settings, so if the user has restricted your app, your tasks won’t run at all.
A news app’s breaking-news alert arrived after the event ended because WorkManager deferred the task during a 45-minute Doze window. The app used WorkManager to fetch the latest headlines and send a local notification, but the delay made the alert irrelevant. WorkManager is efficient, but it’s not predictable.
The trade-off is battery efficiency versus predictable delivery. To log missed executions, use WorkManager.getWorkInfoByIdLiveData() to track the state of your tasks. If a task is repeatedly deferred, switch to AlarmManager for time-sensitive alerts. AlarmManager bypasses Doze mode for exact alarms, but it’s less battery-efficient. Use it sparingly—only for critical notifications.
How to test if your notifications are actually being throttled
To test Doze mode, connect a device and run adb shell dumpsys deviceidle force-idle. This forces the device into Deep Doze immediately. Send a test notification and check if it arrives. If it doesn’t, your app is being throttled. Use Battery Historian to visualise when your app was restricted. The timeline shows Doze phases, app standby buckets, and battery optimisation states.
A ride-hailing app’s driver alert failed during a 30-minute test run because the device entered Deep Doze. The app used normal-priority FCM, which was deferred. The fix was to switch to high-priority FCM and add a foreground service. Battery Historian confirmed the issue by showing the app was restricted during the test.
The trade-off is between lab testing and real-world conditions. Lab testing catches obvious failures, but real-world conditions vary. Instrument your app to log when notifications are sent and received. If a notification is sent but not received within 5 minutes, assume it was throttled. Use this data to adjust your delivery strategy.
Why high-priority FCM isn’t a silver bullet
High-priority FCM bypasses Doze mode, but only if your app meets two conditions: it has a foreground service running, or the user interacted with it in the last 5 minutes. A background music app’s pause notification was delayed because the app didn’t meet either condition. The notification arrived 10 minutes late, after the user unlocked the device.
A messaging app’s typing indicator failed after 10 minutes of inactivity for the same reason. The app used high-priority FCM, but the device was in Deep Doze, and the user hadn’t interacted with the app recently. High-priority FCM isn’t a guarantee—it’s a hint to Android that the message is time-sensitive.
The trade-off is battery drain versus instant delivery. High-priority FCM wakes the device and drains battery. Use it only for critical notifications, like fraud alerts or ride-hailing requests. For less urgent messages, fall back to normal-priority FCM and accept the delay. Log how often high-priority FCM succeeds and fails to measure its impact on battery life.
What happens when you ignore the Android 8+ notification channels requirement
Android 8+ requires every notification to be assigned to a channel. If you post a notification to a non-existent channel, it’s silently dropped. A travel app’s boarding-pass alert vanished after an Android 10 upgrade because the channel it used was deleted during a refactor. The app didn’t crash—it just didn’t show the notification.
To avoid this, create channels at runtime and check if they exist before posting notifications. Use adb shell cmd notification list-channels to list all channels and their importance levels. If a channel is missing, recreate it. The trade-off is between creating too many channels and giving users control. Too many channels overwhelm users; too few limit customisation. Aim for 3-5 channels per app, grouped by function (e.g., alerts, promotions, updates).
Migrate existing channels by checking if they exist before posting notifications. If a channel is missing, recreate it with the same ID and settings. This ensures backward compatibility and prevents silent failures.
How to build a fallback system that catches missed notifications
A fallback system has three components: a local alarm, a server-side timestamp, and a sync-on-foreground check. The local alarm triggers a check for missed notifications when the app next opens. The server-side timestamp records when the notification was sent. The sync-on-foreground check compares the timestamp with the last time the app was opened.
A food-delivery app’s order confirmation arrived 2 hours late because the device was in Deep Doze. The fallback system detected the missed notification when the user opened the app and showed it instantly. The trade-off is data usage versus reliability. The fallback system uses a small amount of data to sync timestamps, but it guarantees delivery.
To implement this, use AlarmManager to set a repeating alarm that triggers a BroadcastReceiver. The receiver checks for missed notifications and shows them locally. Use WorkManager to sync timestamps with your server. Throttle the fallback to once per hour to minimise data usage. Here’s a code snippet:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, NotificationCheckReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(),
AlarmManager.INTERVAL_HOUR,
pendingIntent
)
This ensures your notifications are delivered, even if Android’s power-saving layers block them initially.
Frequently asked
Test devices often run stock Android, which has fewer restrictions than OEM-customised versions. Xiaomi, Huawei, Samsung, and Oppo add aggressive battery managers that kill background processes, including FCM. Your app may also be in a different Doze state or app standby bucket on user devices, delaying or blocking notifications.
Use `adb shell dumpsys deviceidle` to check Doze state and `adb shell dumpsys power` to see if your app is restricted. For OEM battery managers, check the device’s settings for auto-start or background restrictions. Log these states in your app to correlate with notification failures.
Android 8 (API 26) is the minimum for notification channels, which are required for all notifications. For Doze mode and high-priority FCM, Android 6 (API 23) is the baseline. However, OEM battery managers affect all API levels, so you’ll need OEM-specific fixes regardless of Android version.
No. WorkManager is for deferrable tasks, not real-time push notifications. It respects Doze mode, battery optimisations, and network conditions, which can delay or block your tasks. Use FCM for push notifications and WorkManager for background syncs or data processing.
Android’s power-saving layers (Doze mode, OEM battery managers) prioritise battery life over real-time delivery. These systems don’t log failures, so notifications vanish without trace. You can improve reliability with high-priority FCM, OEM-specific fixes, and fallback systems, but guarantees are impossible due to Android’s design.
- android
- doze mode
- fcm
- mobile apps
- notifications
- workmanager
