Back to blog
Article

Push notifications that survive Android battery optimization on cheap hardware

Push notifications that survive Android battery optimization on cheap hardware
S

StriveBit

4 min readMobile Development

Push notifications that survive Android battery optimization on cheap hardware

The delivery rider's phone has 8% battery and 47 background services competing for CPU time. Xiaomi's MIUI has already killed our app's process twice today. The push notification for the new delivery assignment needs to arrive within 30 seconds or the order gets reassigned.

FCM high-priority messages are supposed to bypass doze mode. In practice, OEMs have their own ideas.

The OEM problem

Stock Android honors FCM priority correctly. Budget Android does not. Xiaomi, Oppo, Vivo, Samsung, and others each ship custom battery managers that kill apps they consider inactive. The definition of "inactive" varies — sometimes it's 20 minutes without a foreground service, sometimes it's a whitelist the user has to manually toggle.

We cannot patch around this from the JavaScript side of React Native. The fix lives partly in native code and partly in user education.

What actually works

We use `react-native-push-notification` for the JS API, but the critical work is in `AndroidManifest.xml` and a small native module.

First, request the battery optimization exemption at runtime:

Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);

This prompts the user to whitelist the app. On most OEMs, it opens the right settings screen. On Xiaomi, it opens a generic battery settings page, and the user has to find the autostart toggle separately.

We show a one-time instruction screen with a screenshot of the specific settings page for the detected OEM. Detecting the OEM is a string match on `Build.MANUFACTURER`.

Second, we keep a foreground service running with a persistent notification. This is aggressive and users dislike it, but it is the only reliable way to prevent process death on Xiaomi devices. The notification is low-priority and minimizable, but it stays in the tray.

Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle("Field app active")
    .setSmallIcon(R.drawable.ic_notification)
    .setPriority(NotificationCompat.PRIORITY_LOW)
    .build();
startForeground(NOTIFICATION_ID, notification);

Third, we send FCM messages with `priority: "high"` and a `time_to_live` of 60 seconds. High priority wakes the device from doze. The TTL ensures stale notifications expire rather than arriving late and confusing the field staff.

What does not work

We tried `react-native-background-fetch` for periodic wake-ups. It works on stock Android and fails on Xiaomi after roughly 20 minutes. The library is well-maintained, but the problem is not the library — it is the OS killing the process.

We tried sending a heartbeat push every 5 minutes to keep the FCM connection alive. It worked for a week, then Google started throttling our high-priority quota. High-priority messages are meant for user-visible content, not keepalives.

We tried telling clients to buy better phones. This works when the client is the one carrying the phone. When the phone belongs to a delivery contractor paid per assignment, it does not.

The tradeoff

The foreground service notification is the part clients push back on. Users see a persistent notification and worry something is wrong. We label it clearly — "Field app active" — and make it minimizable on Android 8+. The alternative is missed deliveries, which costs more than a notification icon.

Battery impact is real but small. Our foreground service uses roughly 0.4% battery per hour on a Redmi 9A we keep on the test bench. Over a 10-hour shift, that is 4%. Acceptable for a phone that is already at 8% by noon.

The OEM detection and instruction screen adds about 300 lines of native code to our React Native project. We maintain it because the alternative is a support ticket every time a new field staffer joins and their phone kills the app on day one.

Back to all articles

Ready to build something great?

We help ambitious teams build software that lasts. If you're interested in working with us or want to discuss your project, let's connect.

Get in touch