You’ve Got a Battery‑Draining Background Location Problem
I remember the night I was debugging a delivery app that kept draining the device in the background. The GPS was on 24/7, even when the user had the app closed. The battery hit 10 % in two hours. I had to pull the plug. That’s why I’m writing this for you.
Introduction
Imagine you’re tracking a courier in real time. You want the location every few minutes, but you don’t want to kill the battery. Android 14 tightened background location limits, so the old tricks no longer work. I’ve spent the last month wrestling with WorkManager, and I’ve finally nailed a pattern that keeps the battery happy while still delivering timely updates.
Understanding Android 14 Background Location Limits
Android 14 introduces stricter rules for background services. If a foreground service runs more than 15 minutes, the system throttles it. Background location requests now require the app to be in the foreground or use a foreground service with a persistent notification. If you ignore these, you’ll see a warning in Logcat:
W/LocationManager: Background location access is restricted. Request denied.
Tip: Always check the permission state before starting location updates.
// Wrong: Starting location updates without permission check
val locationRequest = LocationRequest.create().apply {
interval = 5000
}
fusedLocationClient.requestLocationUpdates(locationRequest, callback, null)
Fix:
// Right: Verify permission first
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
fusedLocationClient.requestLocationUpdates(locationRequest, callback, null)
} else {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_CODE)
}
Why WorkManager Is the Ideal Scheduler
WorkManager is built for reliable, battery‑friendly background work. It respects Doze, app standby, and system constraints. Unlike a plain Handler or AlarmManager, WorkManager queues tasks and retries on failure. For location, you can schedule periodic work that only wakes the device when the network is available and the battery is above a threshold.
Quick thought: Think of WorkManager as the smart scheduler that knows when the phone is ready to do the job.
Setting Up WorkManager for Location Updates
First, add the dependency:
implementation "androidx.work:work-runtime-ktx:2.9.0"
Create a Worker that pulls the location:
class LocationWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(appContext)
override suspend fun doWork(): Result = coroutineScope {
try {
val location = getLastKnownLocation()
// Send to server or cache
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
private suspend fun getLastKnownLocation(): Location = suspendCancellableCoroutine { cont ->
fusedLocationClient.lastLocation.addOnSuccessListener { loc ->
if (loc != null) cont.resume(loc, null) else cont.resumeWithException(NullPointerException())
}
}
}
Schedule it:
val workRequest = PeriodicWorkRequestBuilder<LocationWorker>(15, TimeUnit.MINUTES)
.setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiresDeviceIdle(false)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"LocationUpdates",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
What NOT to do: Don’t use a
TimerorHandlerfor periodic location. It won’t respect Doze and will drain the battery.
// Wrong: Timer wakes every 5 minutes regardless of Doze
Timer().scheduleAtFixedRate({ getLocation() }, 0, 5 * 60 * 1000)
Fix: Use WorkManager as shown above.
Best Practices to Minimize Battery Drain
Batch updates: Collect several location points before sending them.
Use coarse location when fine accuracy isn’t needed.
Adjust interval: 15 minutes is a sweet spot for most delivery apps.
Avoid foreground services unless you need real‑time updates.
Check battery level before starting a request.
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
if (batteryLevel < 20) {
// Skip this cycle, maybe log or notify
}
Real‑World Analogy: Scheduling Like a Smart Thermostat
Think of WorkManager as your thermostat. It only turns the heater on when the temperature drops below a set point. Similarly, WorkManager only wakes the phone when the constraints are met. If the phone is in Doze, it’ll hold off. If the battery is low, it’ll wait. You get consistent, efficient updates without the heat (battery drain).
Personal note: I once had a thermostat that tried to heat every hour. It was expensive. Switching to a scheduled mode saved me a fortune. Same principle applies to battery.
Testing & Monitoring Battery Impact
Use Android Studio Profiler:
Run your app on a real device.
Open the Battery tab.
Look for
Locationevents andWorkManagerjobs.Compare with a baseline (no background work).
adb shell dumpsys battery
Check the logcat for WorkManager logs:
I/WorkManager: Enqueued WorkInfo: LocationUpdates
If you see frequent Location events when the app is in the background, you’re probably not respecting constraints.
Common Pitfalls & Debugging Tips
Missing permission: Always double‑check that
ACCESS_FINE_LOCATIONis granted.Doze mode: If your work still runs during Doze, you’re probably using
setRequiresDeviceIdle(false).Multiple workers: Enqueue a unique work name to avoid duplicates.
Testing on emulators: Emulators may not simulate Doze properly. Use a real device.
Debugging tip: Add a log inside
doWork()to see when it fires.
Log.d("LocationWorker", "Fetching location at ${System.currentTimeMillis()}")
If you see logs every minute, you’re not using PeriodicWorkRequestBuilder.
Conclusion & Next Steps
I’ve shown you how Android 14’s background limits change the game, why WorkManager is the right tool, and how to set it up for battery‑efficient location updates. The next step? Integrate a server endpoint that receives batched locations and uses them to update your delivery dashboard. Also, consider adding a foreground notification if you need real‑time precision.
If you want to dive deeper, check out the Fix Android 15 Beta Verifying Apps Stall – Quick Guide article for how to handle new Android 15 quirks. Or look at Debug java.lang.NoClassDefFoundError in Gradle Multi-Module if you hit build issues while adding WorkManager.
Quick Summary
Android 14 limits background location; you need WorkManager or a foreground service.
WorkManager respects Doze, battery, and constraints.
Batch and coarse location reduce drain.
Test on real devices; use profiler and logs.
I’ve been in this situation more times than I can count, and this pattern has saved me hours of debugging and, more importantly, kept my users’ batteries alive.
FAQs
Q1: How do I know if WorkManager is respecting Doze mode?
A1: Check the Battery tab in Android Studio Profiler. If location events appear during Doze, your constraints are wrong.
Q2: Can I use setRequiresBatteryNotLow(false) to get updates even when battery is low?
A2: Yes, but you’ll drain the battery faster. Use it only if you truly need the data.
Q3: What happens if the device is in airplane mode?
A3: WorkManager will still run the job, but fusedLocationClient will return null. Handle that gracefully.
Q4: Is there a way to get higher accuracy without a foreground service?
A4: You can request PRIORITY_HIGH_ACCURACY in the LocationRequest, but the system may still throttle it in the background.
Q5: How do I cancel the periodic work if the user disables location?
A5: Call WorkManager.getInstance(context).cancelUniqueWork("LocationUpdates").




