Battery, wake locks, and power-aware scheduling
Battery cost is driven by keeping CPU awake, radios active, sensors/GPS/camera, screen/GPU, high-frequency callbacks, and thermal work. Optimize the system behavior, not only method CPU time.
Radio tail and batching
Ten tiny network calls spread across minutes can cost more than one batch because the radio repeatedly wakes/remains in a high-power state. Batch deferrable telemetry/sync, compress appropriately, cache, use server push rather than polling, and avoid retries that synchronize across devices (add jitter).
Scheduler owns wakefulness where possible
WorkManager/JobScheduler/DownloadManager manage wake locks for scheduled execution. Prefer them to manually holding a partial wake lock.
If a direct wake lock is genuinely necessary:
val wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"expense:receipt-upload",
).apply { setReferenceCounted(false) }
try {
wakeLock.acquire(2 * 60_000L) // timeout safety
performBoundedOperation()
} finally {
if (wakeLock.isHeld) wakeLock.release()
}
Use a developer-readable tag, shortest scope, timeout, and finally. Never use it to fight Doze for routine polling.
Location/sensors
- request only while feature needs it;
- lower priority/frequency and add minimum distance;
- batch samples for delayed processing;
- use geofence/passive/current location rather than continuous GPS where suitable;
- unregister every listener/callback;
- adapt trip/foreground/background phases.
Background work
Use constraints (network, charging, battery not low) only when product permits, unique work to deduplicate, exponential backoff + jitter, bounded retries, and idempotency. A periodic schedule is not an exact timer. Respect standby/restricted state and user battery choices.
Measure power
Establish a repeatable long-running scenario and compare battery historian/system traces/power rails where available, wake locks, jobs/alarms, network bytes/wakeups, sensor duration, CPU, thermal throttling, and vitals. Battery percentage from one uncontrolled run is noisy.
Thermal behavior
Sustained CPU/GPU/camera/network can heat devices and throttle, increasing latency and energy. Test long sessions, not only 30-second benchmarks. Degrade frame rate/quality/model frequency when product allows and surface essential state.
Common mistakes
- ❌ poll every minute for updates.
- ✅ push signal + reconcile, or batch durable sync.
- ❌ retry all failed jobs immediately on connectivity return.
- ✅ constraints, exponential backoff, jitter, and server load policy.
- ❌ leave high-accuracy location active while screen is hidden.
- ✅ phase-aware ownership/foreground service only for explicit ongoing task.
- ❌ acquire indefinite wake lock in service.
- ✅ let scheduler own it or use bounded
try/finallylock.
Sources
Interview question
Why does batching save battery? It reduces wakeups and lets CPU/radios remain in low-power states longer, trading latency for energy efficiency.