We have all been there: you build a utility app that relies on precise location or time-based triggers, only to find that it works perfectly on your Pixel but dies silently on a Samsung or Xiaomi device. When I started building Muffle, an app designed to automate sound profiles based on prayer times and GPS, I realized that standard AlarmManager usage wasn't enough to survive aggressive battery optimizations.
The Problem with OEM Kill-Switches
Modern Android versions enforce strict background execution limits. If your app isn't a high-priority foreground service, OEMs will frequently kill your process to save a few milliwatts of battery. For Muffle, if the process dies, the user misses their silent profile trigger, which defeats the entire purpose of the app. I had to move away from relying on a long-running background service and rethink my architecture entirely.
Moving to WorkManager with Expedited Jobs
Instead of a persistent service, I transitioned the core logic to WorkManager. By utilizing ExistingPeriodicWorkPolicy.UPDATE, I ensure that the scheduling remains consistent even across reboots. However, WorkManager alone can be delayed by Doze mode. To combat this, I implemented setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) for critical profile switches. This tells the system that the work is time-sensitive.
kotlin
val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES)
.setConstraints(Constraints.Builder().build())
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
Leveraging Foreground Services with Notifications
For features requiring immediate precision—like geofencing—I had to accept that a persistent notification is non-negotiable. To keep the app from being perceived as 'spammy,' I designed the notification to be low-priority, showing only when a profile is actively being managed.
I also had to handle the onTaskRemoved callback in my Service implementation. By calling startService again with a sticky intent, I can ensure the service restarts if the system kills it, though this is a last resort. I also guide users to dontkillmyapp.com settings within the app so they can manually disable battery optimizations for Muffle, which remains the most reliable way to ensure the app functions as intended.
Conclusion
Reliability on Android isn't about fighting the OS; it's about playing by the rules of the battery manager. By combining WorkManager for scheduled tasks and a transparent, user-managed foreground service for geofencing, Muffle remains stable without draining the user's battery. Building in public means acknowledging that sometimes, the platform limitations are the hardest part of the project.
Top comments (0)